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: rpc.undo/redo with non-instant confirmations #483

Merged
merged 1 commit into from
May 4, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 25 additions & 11 deletions brownie/network/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,21 +269,29 @@ def deploy(
revert_data = None
except ValueError as e:
txid, revert_data = _raise_or_return_tx(e)
tx = TransactionReceipt(
receipt = TransactionReceipt(
txid, self, name=contract._name + ".constructor", revert_data=revert_data
)
add_thread = threading.Thread(target=contract._add_from_tx, args=(tx,), daemon=True)
add_thread = threading.Thread(target=contract._add_from_tx, args=(receipt,), daemon=True)
add_thread.start()

if rpc.is_active():
rpc._add_to_undo_buffer(
self.deploy,
(contract, *args),
{"amount": amount, "gas_limit": gas_limit, "gas_price": gas_price},
undo_thread = threading.Thread(
target=rpc._add_to_undo_buffer,
args=(
receipt,
self.deploy,
(contract, *args),
{"amount": amount, "gas_limit": gas_limit, "gas_price": gas_price},
),
daemon=True,
)
if tx.status != 1:
return tx
undo_thread.start()

if receipt.status != 1:
return receipt
add_thread.join()
return contract.at(tx.contract_address)
return contract.at(receipt.contract_address)

def estimate_gas(self, to: "Accounts" = None, amount: int = 0, data: str = None) -> int:
"""Estimates the gas cost for a transaction. Raises VirtualMachineError
Expand Down Expand Up @@ -350,10 +358,16 @@ def transfer(
except ValueError as e:
txid, revert_data = _raise_or_return_tx(e)

receipt = TransactionReceipt(txid, self, silent=silent, revert_data=revert_data)
if rpc.is_active():
rpc._add_to_undo_buffer(self.transfer, (to, amount, gas_limit, gas_price, data), {})
undo_thread = threading.Thread(
target=rpc._add_to_undo_buffer,
args=(receipt, self.transfer, (to, amount, gas_limit, gas_price, data), {}),
daemon=True,
)
undo_thread.start()

return TransactionReceipt(txid, self, silent=silent, revert_data=revert_data)
return receipt


class Account(_PrivateKeyAccount):
Expand Down
70 changes: 38 additions & 32 deletions brownie/network/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import atexit
import gc
import sys
import threading
import time
import weakref
from subprocess import DEVNULL, PIPE
Expand Down Expand Up @@ -48,6 +49,7 @@ def __init__(self) -> None:
self._snapshot_id: Union[int, Optional[bool]] = False
self._reset_id: Union[int, bool] = False
self._current_id: Union[int, bool] = False
self._undo_lock = threading.Lock()
self._undo_buffer: List = []
self._redo_buffer: List = []
atexit.register(self._at_exit)
Expand Down Expand Up @@ -186,13 +188,15 @@ def _revert(self, id_: int) -> int:
_notify_registry()
return id_

def _add_to_undo_buffer(self, fn: Any, args: Tuple, kwargs: Dict) -> None:
self._undo_buffer.append((self._current_id, fn, args, kwargs))
if self._redo_buffer and (fn, args, kwargs) == self._redo_buffer[-1]:
self._redo_buffer.pop()
else:
self._redo_buffer.clear()
self._current_id = self._snap()
def _add_to_undo_buffer(self, tx: Any, fn: Any, args: Tuple, kwargs: Dict) -> None:
with self._undo_lock:
tx._confirmed.wait()
self._undo_buffer.append((self._current_id, fn, args, kwargs))
if self._redo_buffer and (fn, args, kwargs) == self._redo_buffer[-1]:
self._redo_buffer.pop()
else:
self._redo_buffer.clear()
self._current_id = self._snap()

def undo(self, num: int = 1) -> str:
"""
Expand All @@ -203,19 +207,20 @@ def undo(self, num: int = 1) -> str:
num : int, optional
Number of transactions to undo.
"""
if num < 1:
raise ValueError("num must be greater than zero")
if not self._undo_buffer:
raise ValueError("Undo buffer is empty")
if num > len(self._undo_buffer):
raise ValueError(f"Undo buffer contains {len(self._undo_buffer)} items")

for i in range(num, 0, -1):
id_, fn, args, kwargs = self._undo_buffer.pop()
self._redo_buffer.append((fn, args, kwargs))

self._current_id = self._revert(id_)
return f"Block height at {web3.eth.blockNumber}"
with self._undo_lock:
if num < 1:
raise ValueError("num must be greater than zero")
if not self._undo_buffer:
raise ValueError("Undo buffer is empty")
if num > len(self._undo_buffer):
raise ValueError(f"Undo buffer contains {len(self._undo_buffer)} items")

for i in range(num, 0, -1):
id_, fn, args, kwargs = self._undo_buffer.pop()
self._redo_buffer.append((fn, args, kwargs))

self._current_id = self._revert(id_)
return f"Block height at {web3.eth.blockNumber}"

def redo(self, num: int = 1) -> str:
"""
Expand All @@ -226,18 +231,19 @@ def redo(self, num: int = 1) -> str:
num : int, optional
Number of transactions to redo.
"""
if num < 1:
raise ValueError("num must be greater than zero")
if not self._redo_buffer:
raise ValueError("Redo buffer is empty")
if num > len(self._redo_buffer):
raise ValueError(f"Redo buffer contains {len(self._redo_buffer)} items")

for i in range(num, 0, -1):
fn, args, kwargs = self._redo_buffer[-1]
fn(*args, **kwargs)

return f"Block height at {web3.eth.blockNumber}"
with self._undo_lock:
if num < 1:
raise ValueError("num must be greater than zero")
if not self._redo_buffer:
raise ValueError("Redo buffer is empty")
if num > len(self._redo_buffer):
raise ValueError(f"Redo buffer contains {len(self._redo_buffer)} items")

for i in range(num, 0, -1):
fn, args, kwargs = self._redo_buffer[-1]
fn(*args, **kwargs)

return f"Block height at {web3.eth.blockNumber}"

def is_active(self) -> bool:
"""Returns True if Rpc client is currently active."""
Expand Down