Skip to content

Commit 856da17

Browse files
authored
Merge pull request #1142 from carver/rpc-eth-estimateGas
Add RPC methods: eth_estimateGas & eth_accounts
2 parents ca897bb + c3c91c6 commit 856da17

File tree

10 files changed

+309
-50
lines changed

10 files changed

+309
-50
lines changed

eth/chains/base.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
ValidationError,
6060
VMNotFound,
6161
)
62+
from eth.utils.spoof import (
63+
SpoofTransaction,
64+
)
6265
from eth.validation import (
6366
validate_block_number,
6467
validate_uint256,
@@ -149,11 +152,12 @@ def from_genesis_header(cls,
149152
#
150153
# VM API
151154
#
152-
def get_vm_class(self, header: BlockHeader) -> Type['BaseVM']:
155+
@classmethod
156+
def get_vm_class(cls, header: BlockHeader) -> Type['BaseVM']:
153157
"""
154158
Returns the VM instance for the given block number.
155159
"""
156-
return self.get_vm_class_for_block_number(header.block_number)
160+
return cls.get_vm_class_for_block_number(header.block_number)
157161

158162
@abstractmethod
159163
def get_vm(self, header: BlockHeader=None) -> 'BaseVM':
@@ -247,7 +251,10 @@ def get_canonical_transaction(self, transaction_hash: Hash32) -> BaseTransaction
247251
# Execution API
248252
#
249253
@abstractmethod
250-
def estimate_gas(self, transaction: BaseTransaction, at_header: BlockHeader=None) -> int:
254+
def estimate_gas(
255+
self,
256+
transaction: Union[BaseTransaction, SpoofTransaction],
257+
at_header: BlockHeader=None) -> int:
251258
raise NotImplementedError("Chain classes must implement this method")
252259

253260
@abstractmethod
@@ -543,7 +550,10 @@ def create_unsigned_transaction(self,
543550
#
544551
# Execution API
545552
#
546-
def estimate_gas(self, transaction: BaseTransaction, at_header: BlockHeader=None) -> int:
553+
def estimate_gas(
554+
self,
555+
transaction: Union[BaseTransaction, SpoofTransaction],
556+
at_header: BlockHeader=None) -> int:
547557
"""
548558
Returns an estimation of the amount of gas the given transaction will
549559
use if executed on top of the block specified by the given header.

eth/vm/base.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,9 @@ def get_uncle_reward(block_number: int, uncle: BaseBlock) -> int:
238238
def create_transaction(self, *args, **kwargs):
239239
raise NotImplementedError("VM classes must implement this method")
240240

241+
@classmethod
241242
@abstractmethod
242-
def create_unsigned_transaction(self, *args, **kwargs):
243+
def create_unsigned_transaction(cls, *args, **kwargs):
243244
raise NotImplementedError("VM classes must implement this method")
244245

245246
@classmethod
@@ -646,11 +647,12 @@ def create_transaction(self, *args, **kwargs):
646647
"""
647648
return self.get_transaction_class()(*args, **kwargs)
648649

649-
def create_unsigned_transaction(self, *args, **kwargs):
650+
@classmethod
651+
def create_unsigned_transaction(cls, *args, **kwargs):
650652
"""
651653
Proxy for instantiating an unsigned transaction for this VM.
652654
"""
653-
return self.get_transaction_class().create_unsigned_transaction(*args, **kwargs)
655+
return cls.get_transaction_class().create_unsigned_transaction(*args, **kwargs)
654656

655657
@classmethod
656658
def get_transaction_class(cls):

tests/trinity/conftest.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ async def ipc_server(
7474
chain_with_block_validation):
7575
'''
7676
This fixture runs a single RPC server over IPC over
77-
the course of all tests. It never needs to be actually
78-
used as a fixture, so it doesn't return (yield) a value.
77+
the course of all tests. It yields the IPC server only for monkeypatching purposes
7978
'''
8079

8180
rpc = RPCServer(chain_with_block_validation, p2p_server.peer_pool)
@@ -84,6 +83,6 @@ async def ipc_server(
8483
asyncio.ensure_future(ipc_server.run(), loop=event_loop)
8584

8685
try:
87-
yield
86+
yield ipc_server
8887
finally:
8988
await ipc_server.cancel()

tests/trinity/core/json-rpc/test_ipc.py

Lines changed: 163 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,39 +35,70 @@ def __len__(self):
3535
return self.peer_count
3636

3737

38-
@pytest.fixture
39-
def p2p_server(monkeypatch, p2p_server, mock_peer_pool):
40-
monkeypatch.setattr(p2p_server, 'peer_pool', mock_peer_pool)
41-
return p2p_server
38+
def id_from_rpc_request(param):
39+
if isinstance(param, bytes):
40+
request = json.loads(param.decode())
41+
if 'method' in request and 'params' in request:
42+
rpc_params = (repr(p) for p in request['params'])
43+
return '%s(%s)' % (request['method'], ', '.join(rpc_params))
44+
else:
45+
return repr(param)
46+
else:
47+
return repr(param)
48+
49+
50+
def can_decode_json(potential):
51+
try:
52+
json.loads(potential.decode())
53+
return True
54+
except json.decoder.JSONDecodeError:
55+
return False
56+
57+
58+
async def get_ipc_response(
59+
jsonrpc_ipc_pipe_path,
60+
request_msg,
61+
event_loop):
62+
assert wait_for(jsonrpc_ipc_pipe_path), "IPC server did not successfully start with IPC file"
63+
64+
reader, writer = await asyncio.open_unix_connection(str(jsonrpc_ipc_pipe_path), loop=event_loop)
65+
66+
writer.write(request_msg)
67+
await writer.drain()
68+
result_bytes = b''
69+
while not can_decode_json(result_bytes):
70+
result_bytes += await asyncio.tasks.wait_for(reader.readuntil(b'}'), 0.25, loop=event_loop)
71+
72+
writer.close()
73+
return json.loads(result_bytes.decode())
4274

4375

4476
@pytest.mark.asyncio
4577
@pytest.mark.parametrize(
46-
'request_msg, mock_peer_pool, expected',
78+
'request_msg, expected',
4779
(
4880
(
4981
b'{}',
50-
MockPeerPool(),
5182
{'error': "Invalid Request: empty"},
5283
),
5384
(
5485
build_request('notamethod'),
55-
MockPeerPool(),
5686
{'error': "Invalid RPC method: 'notamethod'", 'id': 3, 'jsonrpc': '2.0'},
5787
),
88+
(
89+
build_request('eth_accounts'),
90+
{'result': [], 'id': 3, 'jsonrpc': '2.0'},
91+
),
5892
(
5993
build_request('eth_mining'),
60-
MockPeerPool(),
6194
{'result': False, 'id': 3, 'jsonrpc': '2.0'},
6295
),
6396
(
6497
build_request('web3_clientVersion'),
65-
MockPeerPool(),
6698
{'result': construct_trinity_client_identifier(), 'id': 3, 'jsonrpc': '2.0'},
6799
),
68100
(
69101
build_request('web3_sha3', ['0x89987239849872']),
70-
MockPeerPool(),
71102
{
72103
'result': '0xb3406131994d9c859de3c4400e12f630638e1e992c6453358c16d0e6ce2b1a70',
73104
'id': 3,
@@ -76,7 +107,6 @@ def p2p_server(monkeypatch, p2p_server, mock_peer_pool):
76107
),
77108
(
78109
build_request('web3_sha3', ['0x']),
79-
MockPeerPool(),
80110
{
81111
'result': '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470',
82112
'id': 3,
@@ -85,9 +115,119 @@ def p2p_server(monkeypatch, p2p_server, mock_peer_pool):
85115
),
86116
(
87117
build_request('net_version'),
88-
MockPeerPool(),
89118
{'result': '1337', 'id': 3, 'jsonrpc': '2.0'},
90119
),
120+
(
121+
build_request('net_listening'),
122+
{'result': True, 'id': 3, 'jsonrpc': '2.0'},
123+
),
124+
),
125+
)
126+
async def test_ipc_requests(
127+
jsonrpc_ipc_pipe_path,
128+
request_msg,
129+
expected,
130+
event_loop,
131+
ipc_server):
132+
result = await get_ipc_response(jsonrpc_ipc_pipe_path, request_msg, event_loop)
133+
assert result == expected
134+
135+
136+
@pytest.mark.asyncio
137+
@pytest.mark.parametrize(
138+
'request_msg, expected',
139+
(
140+
(
141+
# simple transaction, with all fields inferred
142+
# (except 'to', which must be provided when not creating a contract)
143+
build_request('eth_estimateGas', params=[{'to': '0x' + '00' * 20}, 'latest']),
144+
# simple transactions are correctly identified as 21000 gas during estimation
145+
{'result': hex(21000), 'id': 3, 'jsonrpc': '2.0'},
146+
),
147+
(
148+
# test block number
149+
build_request('eth_estimateGas', params=[{'to': '0x' + '00' * 20}, '0x0']),
150+
{'result': hex(21000), 'id': 3, 'jsonrpc': '2.0'},
151+
),
152+
(
153+
# another simple transaction, with all fields provided
154+
build_request('eth_estimateGas', params=[{
155+
'to': '0x' + '00' * 20,
156+
'from': '0x' + '11' * 20,
157+
'gasPrice': '0x2',
158+
'gas': '0x3',
159+
'value': '0x0',
160+
'data': '0x',
161+
}, 'latest']),
162+
{'result': hex(21000), 'id': 3, 'jsonrpc': '2.0'},
163+
),
164+
(
165+
# try adding garbage data to increase estimate
166+
build_request('eth_estimateGas', params=[{
167+
'to': '0x' + '00' * 20,
168+
'data': '0x01',
169+
}, 'latest']),
170+
{'result': hex(21068), 'id': 3, 'jsonrpc': '2.0'},
171+
),
172+
(
173+
# deploy a tiny contract
174+
build_request('eth_estimateGas', params=[{
175+
'data': '0x3838533838f3',
176+
}, 'latest']),
177+
{'result': hex(65483), 'id': 3, 'jsonrpc': '2.0'},
178+
),
179+
(
180+
# specifying v,r,s is invalid
181+
build_request('eth_estimateGas', params=[{
182+
'v': '0x' + '00' * 20,
183+
'r': '0x' + '00' * 20,
184+
's': '0x' + '00' * 20,
185+
}, 'latest']),
186+
{
187+
'error': "The following invalid fields were given in a transaction: ['r', 's', 'v']. Only ['data', 'from', 'gas', 'gasPrice', 'to', 'value'] are allowed", # noqa: E501
188+
'id': 3,
189+
'jsonrpc': '2.0',
190+
}
191+
),
192+
(
193+
# specifying gas_price is invalid
194+
build_request('eth_estimateGas', params=[{
195+
'gas_price': '0x0',
196+
}, 'latest']),
197+
{
198+
'error': "The following invalid fields were given in a transaction: ['gas_price']. Only ['data', 'from', 'gas', 'gasPrice', 'to', 'value'] are allowed", # noqa: E501
199+
'id': 3,
200+
'jsonrpc': '2.0',
201+
}
202+
),
203+
(
204+
# specifying nonce is invalid
205+
build_request('eth_estimateGas', params=[{
206+
'nonce': '0x01',
207+
}, 'latest']),
208+
{
209+
'error': "The following invalid fields were given in a transaction: ['nonce']. Only ['data', 'from', 'gas', 'gasPrice', 'to', 'value'] are allowed", # noqa: E501
210+
'id': 3,
211+
'jsonrpc': '2.0',
212+
}
213+
),
214+
),
215+
ids=id_from_rpc_request,
216+
)
217+
async def test_estimate_gas_on_ipc(
218+
jsonrpc_ipc_pipe_path,
219+
request_msg,
220+
expected,
221+
event_loop,
222+
ipc_server):
223+
result = await get_ipc_response(jsonrpc_ipc_pipe_path, request_msg, event_loop)
224+
assert result == expected
225+
226+
227+
@pytest.mark.asyncio
228+
@pytest.mark.parametrize(
229+
'request_msg, mock_peer_pool, expected',
230+
(
91231
(
92232
build_request('net_peerCount'),
93233
MockPeerPool(peer_count=1),
@@ -98,31 +238,19 @@ def p2p_server(monkeypatch, p2p_server, mock_peer_pool):
98238
MockPeerPool(peer_count=0),
99239
{'result': '0x0', 'id': 3, 'jsonrpc': '2.0'},
100240
),
101-
(
102-
build_request('net_listening'),
103-
MockPeerPool(),
104-
{'result': True, 'id': 3, 'jsonrpc': '2.0'},
105-
),
106241
),
107242
ids=[
108-
'empty', 'notamethod', 'eth_mining', 'web3_clientVersion',
109-
'web3_sha3_1', 'web3_sha3_2', 'net_version', 'net_peerCount_1',
110-
'net_peerCount_0', 'net_listening_true',
243+
'net_peerCount_1', 'net_peerCount_0',
111244
],
112245
)
113-
async def test_ipc_requests(jsonrpc_ipc_pipe_path,
114-
request_msg,
115-
expected,
116-
event_loop,
117-
ipc_server):
118-
assert wait_for(jsonrpc_ipc_pipe_path), "IPC server did not successfully start with IPC file"
119-
120-
reader, writer = await asyncio.open_unix_connection(str(jsonrpc_ipc_pipe_path), loop=event_loop)
121-
122-
writer.write(request_msg)
123-
await writer.drain()
124-
result_bytes = await asyncio.tasks.wait_for(reader.readuntil(b'}'), 0.25, loop=event_loop)
125-
126-
result = json.loads(result_bytes.decode())
246+
async def test_peer_pool_over_ipc(
247+
monkeypatch,
248+
jsonrpc_ipc_pipe_path,
249+
request_msg,
250+
mock_peer_pool,
251+
expected,
252+
event_loop,
253+
ipc_server):
254+
monkeypatch.setattr(ipc_server.rpc.modules['net'], '_peer_pool', mock_peer_pool)
255+
result = await get_ipc_response(jsonrpc_ipc_pipe_path, request_msg, event_loop)
127256
assert result == expected
128-
writer.close()

tests/trinity/json-fixtures-over-rpc/test_rpc_fixtures.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113

114114
RPC_TRANSACTION_REMAPPERS = {
115115
'data': 'input',
116+
'gasLimit': 'gas',
116117
}
117118

118119
RPC_TRANSACTION_NORMALIZERS = {

trinity/chains/light.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
BaseTransaction,
4747
BaseUnsignedTransaction,
4848
)
49+
from eth.utils.spoof import (
50+
SpoofTransaction,
51+
)
4952
from eth.vm.computation import (
5053
BaseComputation
5154
)
@@ -189,7 +192,10 @@ def apply_transaction(
189192
transaction: BaseTransaction) -> Tuple[BaseBlock, Receipt, BaseComputation]:
190193
raise NotImplementedError("Chain classes must implement " + inspect.stack()[0][3])
191194

192-
def estimate_gas(self, transaction: BaseTransaction, at_header: BlockHeader=None) -> int:
195+
def estimate_gas(
196+
self,
197+
transaction: Union[BaseTransaction, SpoofTransaction],
198+
at_header: BlockHeader=None) -> int:
193199
raise NotImplementedError("Chain classes must implement " + inspect.stack()[0][3])
194200

195201
def import_block(self, block: BaseBlock, perform_validation: bool=True) -> BaseBlock:

0 commit comments

Comments
 (0)