mirror of https://github.com/ConsenSys/mythril
commit
ccd6845c03
@ -0,0 +1,181 @@ |
||||
import plyvel |
||||
import binascii |
||||
import rlp |
||||
import hashlib |
||||
from ethereum import utils |
||||
from ethereum.block import BlockHeader, Block |
||||
from mythril.leveldb.state import State, Account |
||||
from mythril.leveldb.eth_db import ETH_DB |
||||
from mythril.ether.ethcontract import ETHContract, InstanceList |
||||
|
||||
# Per https://github.com/ethereum/go-ethereum/blob/master/core/database_util.go |
||||
# prefixes and suffixes for keys in geth |
||||
headerPrefix = b'h' # headerPrefix + num (uint64 big endian) + hash -> header |
||||
bodyPrefix = b'b' # bodyPrefix + num (uint64 big endian) + hash -> block body |
||||
numSuffix = b'n' # headerPrefix + num (uint64 big endian) + numSuffix -> hash |
||||
blockHashPrefix = b'H' # blockHashPrefix + hash -> num (uint64 big endian) |
||||
# known geth keys |
||||
headHeaderKey = b'LastHeader' # head (latest) header hash |
||||
|
||||
def _formatBlockNumber(number): |
||||
''' |
||||
formats block number to uint64 big endian |
||||
''' |
||||
return utils.zpad(utils.int_to_big_endian(number), 8) |
||||
|
||||
def _encode_hex(v): |
||||
''' |
||||
encodes hash as hex |
||||
''' |
||||
return '0x' + utils.encode_hex(v) |
||||
|
||||
class EthLevelDB(object): |
||||
''' |
||||
Go-Ethereum LevelDB client class |
||||
''' |
||||
|
||||
def __init__(self, path): |
||||
self.path = path |
||||
self.db = ETH_DB(path) |
||||
self.headBlockHeader = None |
||||
self.headState = None |
||||
self.all_contracts = None |
||||
self.active_contracts = None |
||||
self.instance_lists = None |
||||
|
||||
def get_all_contracts(self): |
||||
''' |
||||
get all contracts |
||||
''' |
||||
if not self.all_contracts: |
||||
self.all_contracts = [] |
||||
self.active_contracts = [] |
||||
self.instance_lists = [] |
||||
state = self._get_head_state() |
||||
accounts = state.get_all_accounts() |
||||
|
||||
for a in accounts: |
||||
if a.code is not None: |
||||
code = _encode_hex(a.code) |
||||
md5 = hashlib.md5() |
||||
md5.update(code.encode('UTF-8')) |
||||
contract_hash = md5.digest() |
||||
contract = ETHContract(code, name=contract_hash.hex()) |
||||
self.all_contracts.append(contract) |
||||
|
||||
if a.balance != 0: |
||||
md5 = InstanceList() |
||||
md5.add(_encode_hex(a.address), a.balance) |
||||
self.instance_lists.append(md5) |
||||
self.active_contracts.append(contract) |
||||
|
||||
return self.all_contracts |
||||
|
||||
def get_active_contracts(self): |
||||
''' |
||||
get all contracts with non-zero balance |
||||
''' |
||||
if not self.active_contracts: |
||||
self.get_all_contracts() # optimized |
||||
return self.active_contracts |
||||
|
||||
def search(self, expression, callback_func): |
||||
''' |
||||
searches through non-zero balance contracts |
||||
''' |
||||
contracts = self.get_active_contracts() |
||||
for i in range(0, len(contracts)): |
||||
if contracts[i].matches_expression(expression): |
||||
m = self.instance_lists[i] |
||||
callback_func(contracts[i].name, contracts[i], m.addresses, m.balances) |
||||
|
||||
def eth_getBlockHeaderByNumber(self, number): |
||||
''' |
||||
gets block header by block number |
||||
''' |
||||
hash = self._get_block_hash(number) |
||||
blockNumber = _formatBlockNumber(number) |
||||
return self._get_block_header(hash, blockNumber) |
||||
|
||||
def eth_getBlockByNumber(self, number): |
||||
''' |
||||
gets block body by block number |
||||
''' |
||||
blockHash = self._get_block_hash(number) |
||||
blockNumber = _formatBlockNumber(number) |
||||
bodyKey = bodyPrefix + blockNumber + blockHash |
||||
blockData = self.db.get(bodyKey) |
||||
body = rlp.decode(blockData, sedes=Block) |
||||
return body |
||||
|
||||
def eth_getCode(self, address): |
||||
''' |
||||
gets account code |
||||
''' |
||||
account = self._get_account(address) |
||||
return _encode_hex(account.code) |
||||
|
||||
def eth_getBalance(self, address): |
||||
''' |
||||
gets account balance |
||||
''' |
||||
account = self._get_account(address) |
||||
return account.balance |
||||
|
||||
def eth_getStorageAt(self, address, position): |
||||
''' |
||||
gets account storage data at position |
||||
''' |
||||
account = self._get_account(address) |
||||
return _encode_hex(utils.zpad(utils.encode_int(account.get_storage_data(position)), 32)) |
||||
|
||||
def _get_head_state(self): |
||||
''' |
||||
gets head state |
||||
''' |
||||
if not self.headState: |
||||
root = self._get_head_block().state_root |
||||
self.headState = State(self.db, root) |
||||
return self.headState |
||||
|
||||
def _get_account(self, address): |
||||
''' |
||||
gets account by address |
||||
''' |
||||
state = self._get_head_state() |
||||
accountAddress = binascii.a2b_hex(utils.remove_0x_head(address)) |
||||
return state.get_and_cache_account(accountAddress) |
||||
|
||||
def _get_block_hash(self, number): |
||||
''' |
||||
gets block hash by block number |
||||
''' |
||||
num = _formatBlockNumber(number) |
||||
hashKey = headerPrefix + num + numSuffix |
||||
return self.db.get(hashKey) |
||||
|
||||
def _get_head_block(self): |
||||
''' |
||||
gets head block header |
||||
''' |
||||
if not self.headBlockHeader: |
||||
hash = self.db.get(headHeaderKey) |
||||
num = self._get_block_number(hash) |
||||
self.headBlockHeader = self._get_block_header(hash, num) |
||||
return self.headBlockHeader |
||||
|
||||
def _get_block_number(self, hash): |
||||
''' |
||||
gets block number by hash |
||||
''' |
||||
numberKey = blockHashPrefix + hash |
||||
return self.db.get(numberKey) |
||||
|
||||
def _get_block_header(self, hash, num): |
||||
''' |
||||
get block header by block header hash & number |
||||
''' |
||||
headerKey = headerPrefix + num + hash |
||||
blockHeaderData = self.db.get(headerKey) |
||||
header = rlp.decode(blockHeaderData, sedes=BlockHeader) |
||||
return header |
@ -0,0 +1,23 @@ |
||||
import plyvel |
||||
from ethereum.db import BaseDB |
||||
from ethereum import utils |
||||
|
||||
class ETH_DB(BaseDB): |
||||
''' |
||||
adopts pythereum BaseDB using plyvel |
||||
''' |
||||
|
||||
def __init__(self, path): |
||||
self.db = plyvel.DB(path) |
||||
|
||||
def get(self, key): |
||||
''' |
||||
gets value for key |
||||
''' |
||||
return self.db.get(key) |
||||
|
||||
def put(self, key, value): |
||||
''' |
||||
puts value for key |
||||
''' |
||||
self.db.put(key, value) |
@ -0,0 +1,130 @@ |
||||
import rlp |
||||
import binascii |
||||
from ethereum.utils import normalize_address, hash32, trie_root, \ |
||||
big_endian_int, address, int256, encode_hex, encode_int, \ |
||||
big_endian_to_int, int_to_addr, zpad, parse_as_bin, parse_as_int, \ |
||||
decode_hex, sha3, is_string, is_numeric |
||||
from rlp.sedes import big_endian_int, Binary, binary, CountableList |
||||
from ethereum import utils |
||||
from ethereum import trie |
||||
from ethereum.trie import Trie |
||||
from ethereum.securetrie import SecureTrie |
||||
|
||||
BLANK_HASH = utils.sha3(b'') |
||||
BLANK_ROOT = utils.sha3rlp(b'') |
||||
|
||||
STATE_DEFAULTS = { |
||||
"txindex": 0, |
||||
"gas_used": 0, |
||||
"gas_limit": 3141592, |
||||
"block_number": 0, |
||||
"block_coinbase": '\x00' * 20, |
||||
"block_difficulty": 1, |
||||
"timestamp": 0, |
||||
"logs": [], |
||||
"receipts": [], |
||||
"bloom": 0, |
||||
"suicides": [], |
||||
"recent_uncles": {}, |
||||
"prev_headers": [], |
||||
"refunds": 0, |
||||
} |
||||
|
||||
|
||||
class Account(rlp.Serializable): |
||||
''' |
||||
adjusted account from ethereum.state |
||||
''' |
||||
|
||||
fields = [ |
||||
('nonce', big_endian_int), |
||||
('balance', big_endian_int), |
||||
('storage', trie_root), |
||||
('code_hash', hash32) |
||||
] |
||||
|
||||
def __init__(self, nonce, balance, storage, code_hash, db, address): |
||||
self.db = db |
||||
self.address = address |
||||
super(Account, self).__init__(nonce, balance, storage, code_hash) |
||||
self.storage_cache = {} |
||||
self.storage_trie = SecureTrie(Trie(self.db)) |
||||
self.storage_trie.root_hash = self.storage |
||||
self.touched = False |
||||
self.existent_at_start = True |
||||
self._mutable = True |
||||
self.deleted = False |
||||
|
||||
@property |
||||
def code(self): |
||||
''' |
||||
code rlp data |
||||
''' |
||||
return self.db.get(self.code_hash) |
||||
|
||||
def get_storage_data(self, key): |
||||
''' |
||||
get storage data |
||||
''' |
||||
if key not in self.storage_cache: |
||||
v = self.storage_trie.get(utils.encode_int32(key)) |
||||
self.storage_cache[key] = utils.big_endian_to_int( |
||||
rlp.decode(v) if v else b'') |
||||
return self.storage_cache[key] |
||||
|
||||
@classmethod |
||||
def blank_account(cls, db, address, initial_nonce=0): |
||||
''' |
||||
creates a blank account |
||||
''' |
||||
db.put(BLANK_HASH, b'') |
||||
o = cls(initial_nonce, 0, trie.BLANK_ROOT, BLANK_HASH, db, address) |
||||
o.existent_at_start = False |
||||
return o |
||||
|
||||
def is_blank(self): |
||||
''' |
||||
checks if is a blank account |
||||
''' |
||||
return self.nonce == 0 and self.balance == 0 and self.code_hash == BLANK_HASH |
||||
|
||||
class State(): |
||||
''' |
||||
adjusted state from ethereum.state |
||||
''' |
||||
|
||||
def __init__(self, db, root): |
||||
self.db = db |
||||
self.trie = Trie(self.db, root) |
||||
self.secureTrie = SecureTrie(self.trie) |
||||
self.journal = [] |
||||
self.cache = {} |
||||
|
||||
def get_and_cache_account(self, address): |
||||
''' |
||||
gets and caches an account for an addres, creates blank if not found |
||||
''' |
||||
if address in self.cache: |
||||
return self.cache[address] |
||||
rlpdata = self.secureTrie.get(address) |
||||
if rlpdata == trie.BLANK_NODE and len(address) == 32: # support for hashed addresses |
||||
rlpdata = self.trie.get(address) |
||||
if rlpdata != trie.BLANK_NODE: |
||||
o = rlp.decode(rlpdata, Account, db=self.db, address=address) |
||||
else: |
||||
o = Account.blank_account( |
||||
self.db, address, 0) |
||||
self.cache[address] = o |
||||
o._mutable = True |
||||
o._cached_rlp = None |
||||
return o |
||||
|
||||
def get_all_accounts(self): |
||||
''' |
||||
iterates through trie to get all items |
||||
''' |
||||
accounts = [] |
||||
for addressHash, rlpdata in self.secureTrie.trie.to_dict().items(): |
||||
if rlpdata != trie.BLANK_NODE: |
||||
accounts.append(rlp.decode(rlpdata, Account, db=self.db, address=addressHash)) |
||||
return accounts |
Loading…
Reference in new issue