Source code for ether_sql.models.state_diff

from sqlalchemy import (
    Column,
    String,
    Numeric,
    ForeignKey,
    Text,
    Integer,
    TIMESTAMP
)
from sqlalchemy.orm import relationship
import logging
import decimal
import csv
from web3.utils.formatters import hex_to_integer
from ether_sql.models import base
from ether_sql.models.storage_diff import StorageDiff
from ether_sql.models.transactions import Transactions
from ether_sql.models.blocks import Blocks
from ether_sql.constants.mainnet import (
    FORK_BLOCK_NUMBER,
    PRE_BYZANTINIUM_REWARD,
    POST_BYZANTINIUM_REWARD,
)
from eth_utils import to_checksum_address
logger = logging.getLogger(__name__)


[docs]class StateDiff(base): """ Class mapping a state_diff table in psql to a difference in state after transactions :param int block_number: Number of the block containing this StateDiff :param timestamp timestamp: Unix time at the mining of this block :param str transaction_hash: The transaction hash if this was created by a transaction :param int transaction_index: Position of this transaction in the transaction list of the block :param str address: Account address where the change occoured :param int balance_diff: Difference in balance due to this row :param int nonce_diff: Difference in nonce due to this row :param str code_from: Initial code of this account :param str code_to: Final code of this account """ __tablename__ = 'state_diff' id = Column(Integer, primary_key=True, autoincrement=True) block_number = Column(Numeric, ForeignKey('blocks.block_number', ondelete='CASCADE')) timestamp = Column(TIMESTAMP) # nullable because some state changes also occour because of miner rewards transaction_hash = Column(String(66), ForeignKey('transactions.transaction_hash', ondelete='CASCADE'), nullable=True, index=True) transaction_index = Column(Numeric, nullable=True) address = Column(String(42), index=True, nullable=False) balance_diff = Column(Numeric, nullable=True) nonce_diff = Column(Integer, nullable=True) code_from = Column(Text, nullable=True) code_to = Column(Text, nullable=True) state_diff_type = Column(String(10)) storage_diff = relationship('StorageDiff', backref='state_diff') def to_dict(self): return { 'block_number': self.block_number, 'timestamp': self.timestamp, 'transaction_hash': self.transaction_hash, 'transaction_index': self.transaction_index, 'address': self.address, 'balance_diff': self.balance_diff, 'nonce_diff': self.nonce_diff, 'code_from': self.code_from, 'code_to': self.code_to, 'state_diff_type': self.state_diff_type, } def _parseStateDiff(account_state, type): state_from = None state_to = None state_diff = None assert len(account_state) == 1 if isinstance(account_state, dict): key = list(account_state) if key[0] == '*': if type == 'code': state_from = account_state['*']['from'] state_to = account_state['*']['to'] else: state_diff = hex_to_integer(account_state['*']['to']) - \ hex_to_integer(account_state['*']['from']) elif key[0] == '+': if type == 'code': state_to = account_state['+'] else: state_diff = hex_to_integer(account_state['+']) elif key[0] == '-': if type == 'code': state_from = account_state['-'] else: state_diff = -1*hex_to_integer(account_state['-']) else: raise ValueError('Unknown key {} in account state'.format(key)) elif account_state == '=': logger.debug('No change in type'.format(type)) else: raise ValueError('Unknown account state {}'.format(account_state)) return state_from, state_to, state_diff
[docs] @classmethod def add_state_diff(cls, balance_diff, nonce_diff, code_from, code_to, address, transaction_hash, transaction_index, block_number, timestamp, miner=None, fees=None, state_diff_type=None): """ Creates a new state_diff object """ address = to_checksum_address(address) if nonce_diff == +1: state_diff_type = 'sender' if miner is not None: if address == miner and balance_diff == fees: state_diff_type = 'fees' state_diff = cls(block_number=block_number, timestamp=timestamp, transaction_hash=transaction_hash, transaction_index=transaction_index, address=address, balance_diff=balance_diff, nonce_diff=nonce_diff, code_from=code_from, code_to=code_to, state_diff_type=state_diff_type) return state_diff
[docs] @classmethod def add_state_diff_dict(cls, current_session, state_diff_dict, transaction_hash, transaction_index, block_number, timestamp, miner, fees): """ Creates a bunch of state_diff objects upon receiving them as a dictionary and adds them to the current db_session """ for address in state_diff_dict: balance_from, balance_to, balance_diff = cls._parseStateDiff( state_diff_dict[address]['balance'], 'balance') nonce_from, nonce_to, nonce_diff = cls._parseStateDiff( state_diff_dict[address]['nonce'], 'nonce') code_from, code_to, code_diff = cls._parseStateDiff( state_diff_dict[address]['code'], 'code') state_diff = cls.add_state_diff( balance_diff=balance_diff, nonce_diff=nonce_diff, code_from=code_from, code_to=code_to, address=address, transaction_hash=transaction_hash, transaction_index=transaction_index, block_number=block_number, timestamp=timestamp, miner=miner, fees=fees) current_session.db_session.add(state_diff) if state_diff_dict[address]['storage'] is not {}: current_session.db_session.flush() StorageDiff.add_storage_diff_dict( current_session=current_session, storage_diff_dict=state_diff_dict[address]['storage'], state_diff_id=state_diff.id, address=address, transaction_hash=transaction_hash, transaction_index=transaction_index, block_number=block_number, timestamp=timestamp)
[docs] @classmethod def add_mining_rewards(cls, current_session, block, uncle_list): """ Adds the mining and uncle rewards to the state_diff table """ miner_reward = PRE_BYZANTINIUM_REWARD if block.block_number > FORK_BLOCK_NUMBER['Byzantium']: miner_reward = POST_BYZANTINIUM_REWARD uncle_inclusion_reward = int(len(uncle_list)*miner_reward/32.0) # adding the miner reward state_diff = StateDiff.add_state_diff( balance_diff=miner_reward+uncle_inclusion_reward, nonce_diff=None, code_from=None, code_to=None, address=block.miner, transaction_hash=None, transaction_index=None, block_number=block.block_number, timestamp=block.timestamp, state_diff_type='miner' ) current_session.db_session.add(state_diff) # adding the uncles for uncle in uncle_list: factor = (uncle.uncle_blocknumber + 8 - uncle.current_blocknumber)/8.0 uncle_reward = int(factor*miner_reward) state_diff = StateDiff.add_state_diff( balance_diff=uncle_reward, nonce_diff=None, code_from=None, code_to=None, address=uncle.miner, transaction_hash=None, transaction_index=None, block_number=block.block_number, timestamp=block.timestamp, state_diff_type='uncle' ) current_session.db_session.add(state_diff)
@classmethod def parse_genesis_rewards(cls, current_session, block): with open('ether_sql/constants/genesis_rewards.csv', 'r', encoding='utf-8') as genesis_rewards: reader = csv.reader(genesis_rewards) for row in reader: state_diff = StateDiff.add_state_diff( balance_diff=row[1], nonce_diff=None, code_from=None, code_to=None, address=row[0], transaction_hash=None, transaction_index=None, block_number=block.block_number, timestamp=block.timestamp, state_diff_type='genesis') current_session.db_session.add(state_diff)