Event, Block, and Transaction Filtering

This commit is contained in:
Piper Merriam 2016-07-31 08:51:34 -06:00
parent 12334f6f93
commit 18eedaaaf6
18 changed files with 1064 additions and 26 deletions

View File

@ -282,3 +282,104 @@ def StringContract(web3_tester, string_contract_abi_def):
code_runtime=string_contract_abi_def["code_runtime"],
source=string_contract_abi_def["source"],
)
CONTRACT_EMITTER_SOURCE = textwrap.dedent(("""
contract Emitter {
event LogAnonymous() anonymous;
event LogNoArguments();
event LogSingleArg(uint arg0);
event LogDoubleArg(uint arg0, uint arg1);
event LogTripleArg(uint arg0, uint arg1, uint arg2);
event LogQuadrupleArg(uint arg0, uint arg1, uint arg2, uint arg3);
// Indexed
event LogSingleWithIndex(uint indexed arg0);
event LogDoubleWithIndex(uint arg0, uint indexed arg1);
event LogTripleWithIndex(uint arg0, uint indexed arg1, uint indexed arg2);
event LogQuadrupleWithIndex(uint arg0, uint arg1, uint indexed arg2, uint indexed arg3);
enum WhichEvent {
LogAnonymous,
LogNoArguments,
LogSingleArg,
LogDoubleArg,
LogTripleArg,
LogQuadrupleArg,
LogSingleWithIndex,
LogDoubleWithIndex,
LogTripleWithIndex,
LogQuadrupleWithIndex
}
function logNoArgs(WhichEvent which) public {
if (which == WhichEvent.LogNoArguments) LogNoArguments();
else if (which == WhichEvent.LogAnonymous) LogAnonymous();
else throw;
}
function logSingle(WhichEvent which, uint arg0) public {
if (which == WhichEvent.LogSingleArg) LogSingleArg(arg0);
else if (which == WhichEvent.LogSingleWithIndex) LogSingleWithIndex(arg0);
else throw;
}
function logDouble(WhichEvent which, uint arg0, uint arg1) public {
if (which == WhichEvent.LogDoubleArg) LogDoubleArg(arg0, arg1);
else if (which == WhichEvent.LogDoubleWithIndex) LogDoubleWithIndex(arg0, arg1);
else throw;
}
function logTriple(WhichEvent which, uint arg0, uint arg1, uint arg2) public {
if (which == WhichEvent.LogTripleArg) LogTripleArg(arg0, arg1, arg2);
else if (which == WhichEvent.LogTripleWithIndex) LogTripleWithIndex(arg0, arg1, arg2);
else throw;
}
function logQuadruple(WhichEvent which, uint arg0, uint arg1, uint arg2, uint arg3) public {
if (which == WhichEvent.LogQuadrupleArg) LogQuadrupleArg(arg0, arg1, arg2, arg3);
else if (which == WhichEvent.LogQuadrupleWithIndex) LogQuadrupleWithIndex(arg0, arg1, arg2, arg3);
else throw;
}
}
"""))
CONTRACT_EMITTER_CODE = "0x60606040526102c0806100126000396000f3606060405260e060020a600035046317c0c180811461004757806320f0256e1461008057806390b41d8b146100da5780639c37705314610125578063aa6fd82214610177575b005b61004560043560018114156101b9577f1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c60006060a15b50565b61004560043560243560443560643560843560058514156101d1576060848152608084815260a084905260c08390527ff039d147f23fe975a4254bdf6b1502b8c79132ae1833986b7ccef2638e73fdf991a15b5050505050565b610045600435602435604435600383141561021357606082815260808290527fdf0cb1dea99afceb3ea698d62e705b736f1345a7eee9eb07e63d1f8f556c1bc590604090a15b505050565b610045600435602435604435606435600484141561024e576060838152608083905260a08290527f4a25b279c7c585f25eda9788ac9420ebadae78ca6b206a0e6ab488fd81f550629080a15b50505050565b610045600435602435600282141561028b5760608181527f56d2ef3c5228bf5d88573621e325a4672ab50e033749a601e4f4a5e1dce905d490602090a15b5050565b60008114156101cc5760006060a061007d565b610002565b60098514156101cc5760608481526080849052819083907fa30ece802b64cd2b7e57dabf4010aabf5df26d1556977affb07b98a77ad955b590604090a36100d3565b60078314156101cc57606082815281907f057bc32826fbe161da1c110afcdcae7c109a8b69149f727fc37a603c60ef94ca90602090a2610120565b60088414156101cc576060838152819083907ff16c999b533366ca5138d78e85da51611089cd05749f098d6c225d4cd42ee6ec90602090a3610171565b60068214156101cc57807ff70fe689e290d8ce2b2a388ac28db36fbb0e16a6d89c6804c461f65a1b40bb1560006060a26101b556"
CONTRACT_EMITTER_RUNTIME = "0x606060405260e060020a600035046317c0c180811461004757806320f0256e1461008057806390b41d8b146100da5780639c37705314610125578063aa6fd82214610177575b005b61004560043560018114156101b9577f1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c60006060a15b50565b61004560043560243560443560643560843560058514156101d1576060848152608084815260a084905260c08390527ff039d147f23fe975a4254bdf6b1502b8c79132ae1833986b7ccef2638e73fdf991a15b5050505050565b610045600435602435604435600383141561021357606082815260808290527fdf0cb1dea99afceb3ea698d62e705b736f1345a7eee9eb07e63d1f8f556c1bc590604090a15b505050565b610045600435602435604435606435600484141561024e576060838152608083905260a08290527f4a25b279c7c585f25eda9788ac9420ebadae78ca6b206a0e6ab488fd81f550629080a15b50505050565b610045600435602435600282141561028b5760608181527f56d2ef3c5228bf5d88573621e325a4672ab50e033749a601e4f4a5e1dce905d490602090a15b5050565b60008114156101cc5760006060a061007d565b610002565b60098514156101cc5760608481526080849052819083907fa30ece802b64cd2b7e57dabf4010aabf5df26d1556977affb07b98a77ad955b590604090a36100d3565b60078314156101cc57606082815281907f057bc32826fbe161da1c110afcdcae7c109a8b69149f727fc37a603c60ef94ca90602090a2610120565b60088414156101cc576060838152819083907ff16c999b533366ca5138d78e85da51611089cd05749f098d6c225d4cd42ee6ec90602090a3610171565b60068214156101cc57807ff70fe689e290d8ce2b2a388ac28db36fbb0e16a6d89c6804c461f65a1b40bb1560006060a26101b556"
CONTRACT_EMITTER_ABI = json.loads('[{"constant":false,"inputs":[{"name":"which","type":"uint8"}],"name":"logNoArgs","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"},{"name":"arg2","type":"uint256"},{"name":"arg3","type":"uint256"}],"name":"logQuadruple","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"}],"name":"logDouble","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"},{"name":"arg2","type":"uint256"}],"name":"logTriple","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"}],"name":"logSingle","outputs":[],"type":"function"},{"anonymous":true,"inputs":[],"name":"LogAnonymous","type":"event"},{"anonymous":false,"inputs":[],"name":"LogNoArguments","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"}],"name":"LogSingleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"}],"name":"LogDoubleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":false,"name":"arg2","type":"uint256"}],"name":"LogTripleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":false,"name":"arg2","type":"uint256"},{"indexed":false,"name":"arg3","type":"uint256"}],"name":"LogQuadrupleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"arg0","type":"uint256"}],"name":"LogSingleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":true,"name":"arg1","type":"uint256"}],"name":"LogDoubleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":true,"name":"arg1","type":"uint256"},{"indexed":true,"name":"arg2","type":"uint256"}],"name":"LogTripleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":true,"name":"arg2","type":"uint256"},{"indexed":true,"name":"arg3","type":"uint256"}],"name":"LogQuadrupleWithIndex","type":"event"}]')
@pytest.fixture()
def EMITTER_SOURCE():
return CONTRACT_EMITTER_SOURCE
@pytest.fixture()
def EMITTER_CODE():
return CONTRACT_EMITTER_CODE
@pytest.fixture()
def EMITTER_RUNTIME():
return CONTRACT_EMITTER_RUNTIME
@pytest.fixture()
def EMITTER_ABI():
return CONTRACT_EMITTER_ABI
@pytest.fixture()
def EmitterContract(web3_tester,
EMITTER_SOURCE,
EMITTER_CODE,
EMITTER_RUNTIME,
EMITTER_ABI):
return web3_tester.eth.contract(
abi=EMITTER_ABI,
code=EMITTER_CODE,
code_runtime=EMITTER_RUNTIME,
source=EMITTER_SOURCE,
)

View File

@ -42,7 +42,7 @@ def test_contract_estimateGas(web3, math_contract):
if isinstance(web3.currentProvider, TestRPCProvider):
pytest.skip("The testrpc server doesn't implement `eth_estimateGas`")
increment_abi = math_contract.find_matching_abi('increment', [])
increment_abi = math_contract.find_matching_fn_abi('increment', [])
call_data = function_abi_to_4byte_selector(increment_abi)
gas_estimate = math_contract.estimateGas().increment()

View File

@ -14,7 +14,7 @@ MULTIPLE_FUNCTIONS = json.loads('[{"constant":false,"inputs":[],"name":"a","outp
def test_finds_single_function_without_args(web3_tester):
Contract = web3_tester.eth.contract(SINGLE_FN_NO_ARGS)
abi = Contract.find_matching_abi('a', [])
abi = Contract.find_matching_fn_abi('a', [])
assert abi['name'] == 'a'
assert abi['inputs'] == []
@ -22,7 +22,7 @@ def test_finds_single_function_without_args(web3_tester):
def test_finds_single_function_with_args(web3_tester):
Contract = web3_tester.eth.contract(SINGLE_FN_ONE_ARG)
abi = Contract.find_matching_abi('a', [1234])
abi = Contract.find_matching_fn_abi('a', [1234])
assert abi['name'] == 'a'
assert len(abi['inputs']) == 1
assert abi['inputs'][0]['type'] == 'uint256'
@ -32,7 +32,7 @@ def test_error_when_no_function_name_match(web3_tester):
Contract = web3_tester.eth.contract(SINGLE_FN_NO_ARGS)
with pytest.raises(ValueError):
Contract.find_matching_abi('no_function_name', [1234])
Contract.find_matching_fn_abi('no_function_name', [1234])
@pytest.mark.parametrize(
@ -48,7 +48,7 @@ def test_error_when_no_function_name_match(web3_tester):
def test_finds_function_with_matching_args(web3_tester, arguments, expected_types):
Contract = web3_tester.eth.contract(MULTIPLE_FUNCTIONS)
abi = Contract.find_matching_abi('a', arguments)
abi = Contract.find_matching_fn_abi('a', arguments)
assert abi['name'] == 'a'
assert len(abi['inputs']) == len(expected_types)
assert set(get_abi_input_types(abi)) == set(expected_types)
@ -58,4 +58,4 @@ def test_error_when_duplicate_match(web3_tester):
Contract = web3_tester.eth.contract(MULTIPLE_FUNCTIONS)
with pytest.raises(ValueError):
abi = Contract.find_matching_abi('a', [100])
abi = Contract.find_matching_fn_abi('a', [100])

View File

@ -42,7 +42,7 @@ def test_eth_estimateGas(web3, math_contract):
if isinstance(web3.currentProvider, TestRPCProvider):
pytest.skip("The testrpc server doesn't implement `eth_estimateGas`")
increment_abi = math_contract.find_matching_abi('increment', [])
increment_abi = math_contract.find_matching_fn_abi('increment', [])
call_data = function_abi_to_4byte_selector(increment_abi)
gas_estimate = web3.eth.estimateGas({
'to': math_contract.address,

175
tests/filtering/conftest.py Normal file
View File

@ -0,0 +1,175 @@
import pytest
import json
import textwrap
from sha3 import sha3_256
from web3.providers.rpc import TestRPCProvider
assert sha3_256(b'').hexdigest() == 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
@pytest.fixture(autouse=True)
def skip_testrpc_and_wait_for_mining_start(web3, wait_for_block):
if isinstance(web3.currentProvider, TestRPCProvider):
pytest.skip("No miner interface on eth-testrpc")
wait_for_block(web3)
CONTRACT_EMITTER_SOURCE = textwrap.dedent(("""
contract Emitter {
event LogAnonymous() anonymous;
event LogNoArguments();
event LogSingleArg(uint arg0);
event LogDoubleArg(uint arg0, uint arg1);
event LogTripleArg(uint arg0, uint arg1, uint arg2);
event LogQuadrupleArg(uint arg0, uint arg1, uint arg2, uint arg3);
// Indexed
event LogSingleWithIndex(uint indexed arg0);
event LogDoubleWithIndex(uint arg0, uint indexed arg1);
event LogTripleWithIndex(uint arg0, uint indexed arg1, uint indexed arg2);
event LogQuadrupleWithIndex(uint arg0, uint arg1, uint indexed arg2, uint indexed arg3);
enum WhichEvent {
LogAnonymous,
LogNoArguments,
LogSingleArg,
LogDoubleArg,
LogTripleArg,
LogQuadrupleArg,
LogSingleWithIndex,
LogDoubleWithIndex,
LogTripleWithIndex,
LogQuadrupleWithIndex
}
function logNoArgs(WhichEvent which) public {
if (which == WhichEvent.LogNoArguments) LogNoArguments();
else if (which == WhichEvent.LogAnonymous) LogAnonymous();
else throw;
}
function logSingle(WhichEvent which, uint arg0) public {
if (which == WhichEvent.LogSingleArg) LogSingleArg(arg0);
else if (which == WhichEvent.LogSingleWithIndex) LogSingleWithIndex(arg0);
else throw;
}
function logDouble(WhichEvent which, uint arg0, uint arg1) public {
if (which == WhichEvent.LogDoubleArg) LogDoubleArg(arg0, arg1);
else if (which == WhichEvent.LogDoubleWithIndex) LogDoubleWithIndex(arg0, arg1);
else throw;
}
function logTriple(WhichEvent which, uint arg0, uint arg1, uint arg2) public {
if (which == WhichEvent.LogTripleArg) LogTripleArg(arg0, arg1, arg2);
else if (which == WhichEvent.LogTripleWithIndex) LogTripleWithIndex(arg0, arg1, arg2);
else throw;
}
function logQuadruple(WhichEvent which, uint arg0, uint arg1, uint arg2, uint arg3) public {
if (which == WhichEvent.LogQuadrupleArg) LogQuadrupleArg(arg0, arg1, arg2, arg3);
else if (which == WhichEvent.LogQuadrupleWithIndex) LogQuadrupleWithIndex(arg0, arg1, arg2, arg3);
else throw;
}
}
"""))
CONTRACT_EMITTER_CODE = "0x60606040526102c0806100126000396000f3606060405260e060020a600035046317c0c180811461004757806320f0256e1461008057806390b41d8b146100da5780639c37705314610125578063aa6fd82214610177575b005b61004560043560018114156101b9577f1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c60006060a15b50565b61004560043560243560443560643560843560058514156101d1576060848152608084815260a084905260c08390527ff039d147f23fe975a4254bdf6b1502b8c79132ae1833986b7ccef2638e73fdf991a15b5050505050565b610045600435602435604435600383141561021357606082815260808290527fdf0cb1dea99afceb3ea698d62e705b736f1345a7eee9eb07e63d1f8f556c1bc590604090a15b505050565b610045600435602435604435606435600484141561024e576060838152608083905260a08290527f4a25b279c7c585f25eda9788ac9420ebadae78ca6b206a0e6ab488fd81f550629080a15b50505050565b610045600435602435600282141561028b5760608181527f56d2ef3c5228bf5d88573621e325a4672ab50e033749a601e4f4a5e1dce905d490602090a15b5050565b60008114156101cc5760006060a061007d565b610002565b60098514156101cc5760608481526080849052819083907fa30ece802b64cd2b7e57dabf4010aabf5df26d1556977affb07b98a77ad955b590604090a36100d3565b60078314156101cc57606082815281907f057bc32826fbe161da1c110afcdcae7c109a8b69149f727fc37a603c60ef94ca90602090a2610120565b60088414156101cc576060838152819083907ff16c999b533366ca5138d78e85da51611089cd05749f098d6c225d4cd42ee6ec90602090a3610171565b60068214156101cc57807ff70fe689e290d8ce2b2a388ac28db36fbb0e16a6d89c6804c461f65a1b40bb1560006060a26101b556"
CONTRACT_EMITTER_RUNTIME = "0x606060405260e060020a600035046317c0c180811461004757806320f0256e1461008057806390b41d8b146100da5780639c37705314610125578063aa6fd82214610177575b005b61004560043560018114156101b9577f1e86022f78f8d04f8e3dfd13a2bdb280403e6632877c0dbee5e4eeb259908a5c60006060a15b50565b61004560043560243560443560643560843560058514156101d1576060848152608084815260a084905260c08390527ff039d147f23fe975a4254bdf6b1502b8c79132ae1833986b7ccef2638e73fdf991a15b5050505050565b610045600435602435604435600383141561021357606082815260808290527fdf0cb1dea99afceb3ea698d62e705b736f1345a7eee9eb07e63d1f8f556c1bc590604090a15b505050565b610045600435602435604435606435600484141561024e576060838152608083905260a08290527f4a25b279c7c585f25eda9788ac9420ebadae78ca6b206a0e6ab488fd81f550629080a15b50505050565b610045600435602435600282141561028b5760608181527f56d2ef3c5228bf5d88573621e325a4672ab50e033749a601e4f4a5e1dce905d490602090a15b5050565b60008114156101cc5760006060a061007d565b610002565b60098514156101cc5760608481526080849052819083907fa30ece802b64cd2b7e57dabf4010aabf5df26d1556977affb07b98a77ad955b590604090a36100d3565b60078314156101cc57606082815281907f057bc32826fbe161da1c110afcdcae7c109a8b69149f727fc37a603c60ef94ca90602090a2610120565b60088414156101cc576060838152819083907ff16c999b533366ca5138d78e85da51611089cd05749f098d6c225d4cd42ee6ec90602090a3610171565b60068214156101cc57807ff70fe689e290d8ce2b2a388ac28db36fbb0e16a6d89c6804c461f65a1b40bb1560006060a26101b556"
CONTRACT_EMITTER_ABI = json.loads('[{"constant":false,"inputs":[{"name":"which","type":"uint8"}],"name":"logNoArgs","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"},{"name":"arg2","type":"uint256"},{"name":"arg3","type":"uint256"}],"name":"logQuadruple","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"}],"name":"logDouble","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"},{"name":"arg1","type":"uint256"},{"name":"arg2","type":"uint256"}],"name":"logTriple","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"which","type":"uint8"},{"name":"arg0","type":"uint256"}],"name":"logSingle","outputs":[],"type":"function"},{"anonymous":true,"inputs":[],"name":"LogAnonymous","type":"event"},{"anonymous":false,"inputs":[],"name":"LogNoArguments","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"}],"name":"LogSingleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"}],"name":"LogDoubleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":false,"name":"arg2","type":"uint256"}],"name":"LogTripleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":false,"name":"arg2","type":"uint256"},{"indexed":false,"name":"arg3","type":"uint256"}],"name":"LogQuadrupleArg","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"arg0","type":"uint256"}],"name":"LogSingleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":true,"name":"arg1","type":"uint256"}],"name":"LogDoubleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":true,"name":"arg1","type":"uint256"},{"indexed":true,"name":"arg2","type":"uint256"}],"name":"LogTripleWithIndex","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg0","type":"uint256"},{"indexed":false,"name":"arg1","type":"uint256"},{"indexed":true,"name":"arg2","type":"uint256"},{"indexed":true,"name":"arg3","type":"uint256"}],"name":"LogQuadrupleWithIndex","type":"event"}]')
@pytest.fixture()
def EMITTER_SOURCE():
return CONTRACT_EMITTER_SOURCE
@pytest.fixture()
def EMITTER_CODE():
return CONTRACT_EMITTER_CODE
@pytest.fixture()
def EMITTER_RUNTIME():
return CONTRACT_EMITTER_RUNTIME
@pytest.fixture()
def EMITTER_ABI():
return CONTRACT_EMITTER_ABI
@pytest.fixture()
def EMITTER(EMITTER_CODE,
EMITTER_RUNTIME,
EMITTER_ABI,
EMITTER_SOURCE):
return {
'code': EMITTER_CODE,
'code_runtime': EMITTER_RUNTIME,
'source': EMITTER_SOURCE,
'abi': EMITTER_ABI,
}
@pytest.fixture()
def Emitter(web3, EMITTER):
return web3.eth.contract(**EMITTER)
@pytest.fixture()
def emitter(web3, Emitter, wait_for_transaction, wait_for_block):
wait_for_block(web3)
deploy_txn_hash = Emitter.deploy({'from': web3.eth.coinbase, 'gas': 1000000})
deploy_receipt = wait_for_transaction(deploy_txn_hash)
contract_address = deploy_receipt['contractAddress']
code = web3.eth.getCode(contract_address)
assert code == Emitter.code_runtime
return Emitter(address=contract_address)
class LogFunctions(object):
LogAnonymous = 0
LogNoArguments = 1
LogSingleArg = 2
LogDoubleArg = 3
LogTripleArg = 4
LogQuadrupleArg = 5
LogSingleWithIndex = 6
LogDoubleWithIndex = 7
LogTripleWithIndex = 8
LogQuadrupleWithIndex = 9
@pytest.fixture()
def emitter_event_ids():
return LogFunctions
def event_topic(event_signature):
from web3.utils.string import force_bytes
return force_bytes("0x" + sha3_256(force_bytes(event_signature)).hexdigest())
class LogTopics(object):
LogAnonymous = event_topic("LogAnonymous()")
LogNoArguments = event_topic("LogNoArguments()")
LogSingleArg = event_topic("LogSingleArg(uint256)")
LogSingleWithIndex = event_topic("LogSingleWithIndex(uint256)")
LogDoubleArg = event_topic("LogDoubleArg(uint256,uint256)")
LogDoubleWithIndex = event_topic("LogDoubleWithIndex(uint256,uint256)")
LogTripleArg = event_topic("LogTripleArg(uint256,uint256,uint256)")
LogTripleWithIndex = event_topic("LogTripleWithIndex(uint256,uint256,uint256)")
LogQuadrupleArg = event_topic("LogQuadrupleArg(uint256,uint256,uint256,uint256)")
LogQuadrupleWithIndex = event_topic("LogQuadrupleWithIndex(uint256,uint256,uint256,uint256)")
@pytest.fixture()
def emitter_log_topics():
return LogTopics

View File

@ -0,0 +1,56 @@
import random
import gevent
def test_on_filter_with_only_event_name(web3,
emitter,
wait_for_transaction,
emitter_log_topics,
emitter_event_ids):
seen_logs = []
filter = emitter.on('LogNoArguments', {}, seen_logs.append)
txn_hash = emitter.transact().logNoArgs(emitter_event_ids.LogNoArguments)
txn_receipt = wait_for_transaction(txn_hash)
gevent.sleep(1)
filter.stop_watching(10)
assert len(seen_logs) == 1
assert seen_logs[0]['transactionHash'] == txn_hash
def test_on_filter_with_event_name_and_single_argument(web3,
emitter,
wait_for_transaction,
emitter_log_topics,
emitter_event_ids):
seen_logs = []
filter = emitter.on('LogTripleWithIndex', {'filter': {
'arg1': 2,
}}, seen_logs.append)
txn_hashes = []
txn_hashes.append(
emitter.transact().logTriple(emitter_event_ids.LogTripleWithIndex, 2, 1, 3)
)
txn_hashes.append(
emitter.transact().logTriple(emitter_event_ids.LogTripleWithIndex, 1, 2, 3)
)
txn_hashes.append(
emitter.transact().logTriple(emitter_event_ids.LogTripleWithIndex, 12345, 2, 54321)
)
for txn_hash in txn_hashes:
wait_for_transaction(txn_hash)
gevent.sleep(1)
filter.stop_watching(10)
assert len(seen_logs) == 2
assert {l['transactionHash'] for l in seen_logs} == set(txn_hashes[1:])

View File

@ -0,0 +1,24 @@
import random
import gevent
def test_past_events_filter_with_only_event_name(web3,
emitter,
wait_for_transaction,
emitter_log_topics,
emitter_event_ids):
txn_hash = emitter.transact().logNoArgs(emitter_event_ids.LogNoArguments)
txn_receipt = wait_for_transaction(txn_hash)
seen_logs = []
filter = emitter.pastEvents('LogNoArguments', {}, seen_logs.append)
with gevent.Timeout(5):
while not seen_logs:
gevent.sleep(random.random())
filter.stop_watching(10)
assert len(seen_logs) == 1
assert seen_logs[0]['transactionHash'] == txn_hash

View File

@ -0,0 +1,20 @@
def test_filter_against_latest_blocks(web3, wait_for_block):
seen_blocks = []
txn_filter = web3.eth.filter("latest")
txn_filter.watch(seen_blocks.append)
current_block = web3.eth.blockNumber
wait_for_block(web3, current_block + 3)
txn_filter.stop_watching(3)
# give the gevent threads a moment to catch the latest block.
wait_for_block(web3, web3.eth.blockNumber + 1)
expected_block_hashes = [
web3.eth.getBlock(n)['hash'] for n in range(current_block + 1, current_block + 4)
]
assert len(seen_blocks) > 2
assert set(expected_block_hashes).issuperset(seen_blocks)

View File

@ -0,0 +1,23 @@
def test_filter_against_pending_transactions(web3, wait_for_transaction):
seen_txns = []
txn_filter = web3.eth.filter("pending")
txn_filter.watch(seen_txns.append)
txn_1_hash = web3.eth.sendTransaction({
'from': web3.eth.coinbase,
'to': '0xd3cda913deb6f67967b99d67acdfa1712c293601',
'value': 12345,
})
txn_2_hash = web3.eth.sendTransaction({
'from': web3.eth.coinbase,
'to': '0xd3cda913deb6f67967b99d67acdfa1712c293601',
'value': 54321,
})
wait_for_transaction(txn_1_hash)
wait_for_transaction(txn_2_hash)
txn_filter.stop_watching(30)
assert txn_1_hash in seen_txns
assert txn_2_hash in seen_txns

View File

@ -0,0 +1,20 @@
def test_filter_against_log_events(web3,
emitter,
wait_for_transaction,
emitter_log_topics,
emitter_event_ids):
seen_logs = []
txn_filter = web3.eth.filter({})
txn_filter.watch(seen_logs.append)
txn_hashes = []
txn_hashes.append(emitter.transact().logNoArgs(emitter_event_ids.LogNoArguments))
for txn_hash in txn_hashes:
wait_for_transaction(txn_hash)
txn_filter.stop_watching(30)
assert set(txn_hashes) == set(log['transactionHash'] for log in seen_logs)

View File

@ -0,0 +1,61 @@
import pytest
from web3.utils.abi import (
filter_by_argument_name,
)
ABI = [
{
"constant": False,
"inputs": [],
"name": "func_1",
"outputs": [],
"type": "function",
},
{
"constant": False,
"inputs": [
{"name": "a", "type": "uint256"},
],
"name": "func_2",
"outputs": [],
"type": "function",
},
{
"constant": False,
"inputs": [
{"name": "a", "type": "uint256"},
{"name": "b", "type": "uint256"},
],
"name": "func_3",
"outputs": [],
"type": "function",
},
{
"constant": False,
"inputs": [
{"name": "a", "type": "uint256"},
{"name": "b", "type": "uint256"},
{"name": "c", "type": "uint256"},
],
"name": "func_4",
"outputs": [],
"type": "function",
},
]
@pytest.mark.parametrize(
'argument_names,expected',
(
([], ['func_1', 'func_2', 'func_3', 'func_4']),
(['a'], ['func_2', 'func_3', 'func_4']),
(['a', 'c'], ['func_4']),
(['c'], ['func_4']),
(['b'], ['func_3', 'func_4']),
)
)
def test_filter_by_arguments_1(argument_names, expected):
actual_matches = filter_by_argument_name(argument_names, ABI)
function_names = [match['name'] for match in actual_matches]
assert set(function_names) == set(expected)

View File

@ -0,0 +1,76 @@
import pytest
from web3.utils.abi import (
construct_event_data_set,
)
EVENT_1_ABI = {
"anonymous": False,
"inputs": [
{"indexed": False,"name":"arg0","type":"uint256"},
{"indexed": True,"name":"arg1","type":"uint256"},
{"indexed": True,"name":"arg2","type":"uint256"},
{"indexed": False,"name":"arg3","type":"uint256"},
{"indexed": True,"name":"arg4","type":"uint256"},
{"indexed": False,"name":"arg5","type":"uint256"},
],
"name": "Event_1",
"type":"event",
}
EVENT_1_TOPIC = '0xa7144ed450ecab4a6283d3b1e290ff6c889232d922b84d88203eb7619222fb32'
def hex_and_pad(i):
unpadded_hex_value = hex(i)
return '0x' + unpadded_hex_value[2:].zfill(65 - len(unpadded_hex_value[2:]))
@pytest.mark.parametrize(
'event_abi,arguments,expected',
(
(
EVENT_1_ABI,
{},
[[]],
),
(
EVENT_1_ABI,
{'arg1': 1},
[[]],
),
(
EVENT_1_ABI,
{'arg0': 1},
[[hex_and_pad(1), None, None]],
),
(
EVENT_1_ABI,
{'arg0': [1]},
[[hex_and_pad(1), None, None]],
),
(
EVENT_1_ABI,
{'arg0': [1, 2]},
[
[hex_and_pad(1), None, None],
[hex_and_pad(2), None, None],
],
),
(
EVENT_1_ABI,
{'arg0': [1, 3], 'arg3': [2, 4]},
[
[hex_and_pad(1), hex_and_pad(2), None],
[hex_and_pad(1), hex_and_pad(4), None],
[hex_and_pad(3), hex_and_pad(2), None],
[hex_and_pad(3), hex_and_pad(4), None],
],
),
)
)
def test_construct_event_data_set(event_abi, arguments, expected):
actual = construct_event_data_set(event_abi, arguments)
assert actual == expected

View File

@ -0,0 +1,55 @@
import pytest
from web3.utils.abi import (
event_abi_to_log_topic,
)
from web3.utils.filters import (
construct_event_filter_params,
)
EVENT_1_ABI = {
"anonymous": False,
"inputs": [
{"indexed": False,"name":"arg0","type":"uint256"},
{"indexed": True,"name":"arg1","type":"uint256"},
],
"name": "Event_1",
"type":"event",
}
@pytest.mark.parametrize(
"event_abi,fn_kwargs,expected",
(
(EVENT_1_ABI, {}, {
"topics": ['0xb470a829ed7792f06947f0ca3730a570cb378329ddcf09f2b4efabd6326f51f6'],
}),
(EVENT_1_ABI, {'topics': ['should-be-preserved']}, {
"topics": [
['should-be-preserved'],
['0xb470a829ed7792f06947f0ca3730a570cb378329ddcf09f2b4efabd6326f51f6'],
]
}),
(EVENT_1_ABI, {'contract_address': '0xd3cda913deb6f67967b99d67acdfa1712c293601'}, {
"topics": ['0xb470a829ed7792f06947f0ca3730a570cb378329ddcf09f2b4efabd6326f51f6'],
'address': '0xd3cda913deb6f67967b99d67acdfa1712c293601',
}),
(EVENT_1_ABI, {
'contract_address': '0xd3cda913deb6f67967b99d67acdfa1712c293601',
'address': '0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
}, {
"topics": ['0xb470a829ed7792f06947f0ca3730a570cb378329ddcf09f2b4efabd6326f51f6'],
'address': [
'0xbb9bc244d798123fde783fcc1c72d3bb8c189413',
'0xd3cda913deb6f67967b99d67acdfa1712c293601',
],
}),
(EVENT_1_ABI, {'address': '0xd3cda913deb6f67967b99d67acdfa1712c293601'}, {
"topics": ['0xb470a829ed7792f06947f0ca3730a570cb378329ddcf09f2b4efabd6326f51f6'],
'address': '0xd3cda913deb6f67967b99d67acdfa1712c293601',
}),
),
)
def test_construct_event_filter_params(event_abi, fn_kwargs, expected):
actual = construct_event_filter_params(event_abi, **fn_kwargs)
assert actual == expected

View File

@ -0,0 +1,84 @@
import pytest
from web3.utils.abi import (
construct_event_topic_set,
)
EVENT_1_ABI = {
"anonymous": False,
"inputs": [
{"indexed": False,"name":"arg0","type":"uint256"},
{"indexed": True,"name":"arg1","type":"uint256"},
{"indexed": True,"name":"arg2","type":"uint256"},
{"indexed": False,"name":"arg3","type":"uint256"},
{"indexed": True,"name":"arg4","type":"uint256"},
{"indexed": False,"name":"arg5","type":"uint256"},
],
"name": "Event_1",
"type":"event",
}
EVENT_1_TOPIC = '0xa7144ed450ecab4a6283d3b1e290ff6c889232d922b84d88203eb7619222fb32'
def hex_and_pad(i):
unpadded_hex_value = hex(i)
return '0x' + unpadded_hex_value[2:].zfill(65 - len(unpadded_hex_value[2:]))
@pytest.mark.parametrize(
'event_abi,arguments,expected',
(
(
EVENT_1_ABI,
{},
[[EVENT_1_TOPIC]],
),
(
EVENT_1_ABI,
{'arg0': 1},
[[EVENT_1_TOPIC]],
),
(
EVENT_1_ABI,
{'arg0': 1, 'arg3': [1, 2]},
[[EVENT_1_TOPIC]],
),
(
EVENT_1_ABI,
{'arg1': 1},
[
[EVENT_1_TOPIC, hex_and_pad(1), None, None],
],
),
(
EVENT_1_ABI,
{'arg1': [1, 2]},
[
[EVENT_1_TOPIC, hex_and_pad(1), None, None],
[EVENT_1_TOPIC, hex_and_pad(2), None, None],
],
),
(
EVENT_1_ABI,
{'arg1': [1], 'arg2': [2]},
[
[EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(2), None],
],
),
(
EVENT_1_ABI,
{'arg1': [1, 3], 'arg2': [2, 4]},
[
[EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(2), None],
[EVENT_1_TOPIC, hex_and_pad(1), hex_and_pad(4), None],
[EVENT_1_TOPIC, hex_and_pad(3), hex_and_pad(2), None],
[EVENT_1_TOPIC, hex_and_pad(3), hex_and_pad(4), None],
],
),
)
)
def test_construct_event_topics(event_abi, arguments, expected):
actual = construct_event_topic_set(event_abi, arguments)
assert actual == expected

View File

@ -16,6 +16,9 @@ from web3.utils.formatting import (
add_0x_prefix,
remove_0x_prefix,
)
from web3.utils.types import (
is_array,
)
from web3.utils.string import (
force_bytes,
coerce_return_to_text,
@ -25,12 +28,21 @@ from web3.utils.abi import (
filter_by_type,
filter_by_name,
filter_by_argument_count,
filter_by_argument_name,
filter_by_encodability,
get_abi_input_types,
get_abi_output_types,
get_constructor_abi,
check_if_arguments_can_be_encoded,
function_abi_to_4byte_selector,
event_abi_to_log_topic,
)
from web3.utils.functional import (
compose,
)
from web3.utils.filters import (
construct_event_filter_params,
LogFilter,
)
@ -232,7 +244,7 @@ class Contract(object):
# ABI Helpers
#
@classmethod
def find_matching_abi(cls, fn_name, arguments):
def find_matching_fn_abi(cls, fn_name, arguments):
filters = [
functools.partial(filter_by_name, fn_name),
functools.partial(filter_by_argument_count, arguments),
@ -254,13 +266,30 @@ class Contract(object):
else:
raise ValueError("Multiple functions found")
@classmethod
def find_matching_event_abi(cls, event_name, argument_names):
filter_fn = compose(
functools.partial(filter_by_type, 'event'),
functools.partial(filter_by_name, event_name),
functools.partial(filter_by_argument_name, argument_names),
)
event_abi_candidates = filter_fn(cls.abi)
if len(event_abi_candidates) == 1:
return event_abi_candidates[0]
elif not event_abi_candidates:
raise ValueError("No matching functions found")
else:
raise ValueError("Multiple functions found")
@classmethod
@coerce_return_to_text
def encodeABI(cls, fn_name, arguments, data=None):
"""
encodes the arguments using the Ethereum ABI.
"""
function_abi = cls.find_matching_abi(fn_name, force_obj_to_bytes(arguments))
function_abi = cls.find_matching_fn_abi(fn_name, force_obj_to_bytes(arguments))
return cls._encodeABI(function_abi, arguments, data)
@classmethod
@ -309,17 +338,61 @@ class Contract(object):
return deploy_data
def on(self, event, filters, callback):
def on(self, event_name, default_filter_params=None, *callbacks):
"""
register a callback to be triggered on the appropriate events.
"""
raise NotImplementedError('Not implemented')
if default_filter_params is None:
default_filter_params = {}
def pastEvents(self, event, filters, callback):
argument_filters = default_filter_params.pop('filter', {})
argument_filter_names = list(argument_filters.keys())
event_abi = self.find_matching_event_abi(event_name, argument_filter_names)
filter_params = construct_event_filter_params(
event_abi,
contract_address=self.address,
argument_filters=argument_filters,
**default_filter_params
)
filter = self.web3.eth.filter(filter_params)
if callbacks:
filter.watch(*callbacks)
filter.filter_params = filter_params
return filter
def pastEvents(self, event_name, default_filter_params=None, *callbacks):
"""
register a callback to be triggered on all past events.
"""
raise NotImplementedError('Not implemented')
if default_filter_params is None:
default_filter_params = {}
if 'fromBlock' in default_filter_params or 'toBlock' in default_filter_params:
raise ValueError("Cannot provide `fromBlock` or `toBlock` in `pastEvents` calls")
argument_filters = default_filter_params.pop('filter', {})
argument_filter_names = list(argument_filters.keys())
event_abi = self.find_matching_event_abi(event_name, argument_filter_names)
filter_params = construct_event_filter_params(
event_abi,
contract_address=self.address,
argument_filters=argument_filters,
fromBlock="earliest",
toBlock=self.web3.eth.blockNumber,
**default_filter_params
)
filter = self.web3.eth.filter(filter_params)
if callbacks:
filter.watch(*callbacks)
return filter
def estimateGas(self, transaction=None):
"""
@ -506,7 +579,7 @@ def call_contract_function(contract=None,
if not arguments:
arguments = []
function_abi = contract.find_matching_abi(function_name, arguments)
function_abi = contract.find_matching_fn_abi(function_name, arguments)
function_selector = function_abi_to_4byte_selector(function_abi)
transaction['data'] = contract.encodeABI(
@ -590,7 +663,7 @@ def transact_with_contract_function(contract=None,
if not arguments:
arguments = []
function_abi = contract.find_matching_abi(function_name, arguments)
function_abi = contract.find_matching_fn_abi(function_name, arguments)
function_selector = function_abi_to_4byte_selector(function_abi)
transaction['data'] = contract.encodeABI(
@ -615,7 +688,7 @@ def estimate_gas_for_function(contract=None,
if not arguments:
arguments = []
function_abi = contract.find_matching_abi(function_name, arguments)
function_abi = contract.find_matching_fn_abi(function_name, arguments)
function_selector = function_abi_to_4byte_selector(function_abi)
transaction['data'] = contract.encodeABI(

View File

@ -8,10 +8,16 @@ from web3.utils.encoding import (
)
from web3.utils.types import (
is_integer,
is_string,
)
from web3.utils.functional import (
apply_formatters_to_return,
)
from web3.utils.filters import (
BlockFilter,
TransactionFilter,
LogFilter,
)
from web3.contract import construct_contract_class
@ -218,13 +224,35 @@ class Eth(object):
def estimateGas(self, transaction):
return self.request_manager.request_blocking("eth_estimateGas", [transaction])
def filter(self, *args, **kwargs):
"""
`eth_newFilter`
`eth_newBlockFilter`
`eth_uninstallFilter`
"""
raise NotImplementedError("TODO")
def filter(self, filter_params):
if is_string(filter_params):
if filter_params == "latest":
filter_id = self.request_manager.request_blocking("eth_newBlockFilter", [])
return BlockFilter(self.web3, filter_id)
elif filter_params == "pending":
filter_id = self.request_manager.request_blocking(
"eth_newPendingTransactionFilter", [],
)
return TransactionFilter(self.web3, filter_id)
else:
raise ValueError(
"The filter API only accepts the values of `pending` or "
"`latest` for string based filters"
)
elif isinstance(filter_params, dict):
filter_id = self.request_manager.request_blocking("eth_newFilter", [filter_params])
return LogFilter(self.web3, filter_id)
else:
raise ValueError("Must provide either a string or a valid filter object")
def getFilterChanges(self, filter_id):
return self.request_manager.request_blocking("eth_getFilterChanges", [filter_id])
def getFilterLogs(self, filter_id):
return self.request_manager.request_blocking("eth_getFilterLogs", [filter_id])
def uninstallFilter(self, filter_id):
return self.request_manager.request_blocking("eth_uninstallFilter", [filter_id])
def contract(self, abi, address=None, **kwargs):
contract_class = construct_contract_class(self.web3, abi, **kwargs)

View File

@ -1,9 +1,16 @@
import itertools
from eth_abi.abi import (
process_type,
encode_single,
)
from .encoding import encode_hex
from .crypto import sha3
from .string import coerce_args_to_bytes
from .string import (
coerce_args_to_bytes,
coerce_return_to_text,
)
from .formatting import (
add_0x_prefix,
)
@ -34,6 +41,18 @@ def get_abi_output_types(abi):
return [arg['type'] for arg in abi['outputs']]
def get_abi_input_names(abi):
return [arg['name'] for arg in abi['inputs']]
def get_indexed_event_inputs(event_abi):
return [arg for arg in event_abi['inputs'] if arg['indexed'] is True]
def exclude_indexed_event_inputs(event_abi):
return [arg for arg in event_abi['inputs'] if arg['indexed'] is False]
def filter_by_argument_count(arguments, contract_abi):
return [
abi
@ -43,6 +62,16 @@ def filter_by_argument_count(arguments, contract_abi):
]
def filter_by_argument_name(argument_names, contract_abi):
return [
abi
for abi in contract_abi
if set(argument_names).intersection(
get_abi_input_names(abi)
) == set(argument_names)
]
def is_encodable(_type, value):
try:
base, sub, arrlist = _type
@ -124,7 +153,7 @@ def get_constructor_abi(contract_abi):
raise ValueError("Found multiple constructors.")
def abi_to_4byte_function_selector(function_abi):
def abi_to_signature(function_abi):
function_signature = "{fn_name}({fn_input_types})".format(
fn_name=function_abi['name'],
fn_input_types=','.join([
@ -135,5 +164,95 @@ def abi_to_4byte_function_selector(function_abi):
def function_abi_to_4byte_selector(function_abi):
function_signature = abi_to_4byte_function_selector(function_abi)
function_signature = abi_to_signature(function_abi)
return add_0x_prefix(sha3(function_signature)[:8])
def event_abi_to_log_topic(event_abi):
event_signature = abi_to_signature(event_abi)
return add_0x_prefix(sha3(event_signature))
@coerce_return_to_text
def construct_event_topic_set(event_abi, arguments=None):
if arguments is None:
arguments = {}
if isinstance(arguments, (list, tuple)):
if len(arguments) != len(event_abi['inputs']):
raise ValueError(
"When passing an argument list, the number of arguments must "
"match the event constructor."
)
arguments = {
arg['name']: [arg_value]
for arg, arg_value
in zip(event_abi['inputs'], arguments)
}
normalized_args = {
key: value if is_array(value) else [value]
for key, value in arguments.items()
}
event_topic = event_abi_to_log_topic(event_abi)
indexed_args = get_indexed_event_inputs(event_abi)
zipped_abi_and_args = [
(arg, normalized_args.get(arg['name'], [None]))
for arg in indexed_args
]
encoded_args = [
[
None if option is None else encode_hex(encode_single(arg['type'], option))
for option in arg_options]
for arg, arg_options in zipped_abi_and_args
]
topics = [
[event_topic] + list(permutation)
if any(value is not None for value in permutation)
else [event_topic]
for permutation in itertools.product(*encoded_args)
]
return topics
@coerce_return_to_text
def construct_event_data_set(event_abi, arguments=None):
if arguments is None:
arguments = {}
if isinstance(arguments, (list, tuple)):
if len(arguments) != len(event_abi['inputs']):
raise ValueError(
"When passing an argument list, the number of arguments must "
"match the event constructor."
)
arguments = {
arg['name']: [arg_value]
for arg, arg_value
in zip(event_abi['inputs'], arguments)
}
normalized_args = {
key: value if is_array(value) else [value]
for key, value in arguments.items()
}
indexed_args = exclude_indexed_event_inputs(event_abi)
zipped_abi_and_args = [
(arg, normalized_args.get(arg['name'], [None]))
for arg in indexed_args
]
encoded_args = [
[
None if option is None else encode_hex(encode_single(arg['type'], option))
for option in arg_options]
for arg, arg_options in zipped_abi_and_args
]
topics = [
list(permutation)
if any(value is not None for value in permutation)
else []
for permutation in itertools.product(*encoded_args)
]
return topics

123
web3/utils/filters.py Normal file
View File

@ -0,0 +1,123 @@
import random
import gevent
from .types import (
is_string,
is_array,
)
from .abi import (
construct_event_topic_set,
construct_event_data_set,
)
def construct_event_filter_params(event_abi,
contract_address=None,
argument_filters=None,
topics=None,
fromBlock=None,
toBlock=None,
address=None):
filter_params = {}
if topics is None:
topic_set = construct_event_topic_set(event_abi, argument_filters)
else:
topic_set = [topics] + construct_event_topic_set(event_abi, argument_filters)
if len(topic_set) == 1 and is_array(topic_set[0]):
filter_params['topics'] = topic_set[0]
else:
filter_params['topics'] = topic_set
if address and contract_address:
if is_array(address):
filter_params['address'] = address + [contract_address]
elif is_string(address):
filter_params['address'] = [address, contract_address]
else:
raise ValueError(
"Unsupported type for `address` parameter: {0}".format(type(address))
)
elif address:
filter_params['address'] = address
elif contract_address:
filter_params['address'] = contract_address
if fromBlock is not None:
filter_params['fromBlock'] = fromBlock
if toBlock is not None:
filter_params['toBlock'] = toBlock
return filter_params
class BaseFilter(gevent.Greenlet):
callbacks = None
running = None
stopped = False
def __init__(self, web3, filter_id):
self.web3 = web3
self.filter_id = filter_id
self.callbacks = []
gevent.Greenlet.__init__(self)
def __str__(self):
return "Filter for {0}".format(self.filter_id)
def _run(self):
if self.stopped:
raise ValueError("Cannot restart a Filter")
self.running = True
previous_logs = self.web3.eth.getFilterLogs(self.filter_id)
if previous_logs:
for log in previous_logs:
for callback_fn in self.callbacks:
callback_fn(log)
while self.running:
changes = self.web3.eth.getFilterChanges(self.filter_id)
if changes:
for log in changes:
for callback_fn in self.callbacks:
callback_fn(log)
gevent.sleep(random.random())
def watch(self, *callbacks):
if self.stopped:
raise ValueError("Cannot watch on a filter that has been stopped")
self.callbacks.extend(callbacks)
if not self.running:
self.start()
def stop_watching(self, timeout=0):
self.running = False
self.stopped = True
self.web3.eth.uninstallFilter(self.filter_id)
self.join(timeout)
stopWatching = stop_watching
class BlockFilter(BaseFilter):
pass
class TransactionFilter(BaseFilter):
pass
class LogFilter(BaseFilter):
def get(self, only_changes=True):
if self.running:
raise ValueError(
"Cannot call `get` on a filter object which is actively watching"
)
if only_changes:
return self.web3.eth.getFilterChanges(self.filter_id)
else:
return self.web3.eth.getFilterChanges(self.filter_id)