Skip to content
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
14 changes: 13 additions & 1 deletion raiden/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import math
from enum import Enum

from eth_utils import to_checksum_address
Expand Down Expand Up @@ -31,16 +32,27 @@ class EthClient(Enum):
START_QUERY_BLOCK_KEY = 'DefaultStartBlock'
SNAPSHOT_STATE_CHANGES_COUNT = 500

# An arbitrary limit for transaction size in Raiden, added in PR #1990
TRANSACTION_GAS_LIMIT_UPPER_BOUND = int(0.4 * 3_141_592)

# Used to add a 30% security margin to gas estimations in case the calculations are off
GAS_FACTOR = 1.3
Comment thread
hackaugusto marked this conversation as resolved.
Comment thread
palango marked this conversation as resolved.

# The more pending transfers there are, the more computationally complex
# it becomes to unlock them. Lest an unlocking operation fails because
# not enough gas is available, we define a gas limit for unlock calls
# and limit the number of pending transfers per channel so it is not
# exceeded. The limit is inclusive.
TRANSACTION_GAS_LIMIT = int(0.4 * 3141592)
UNLOCK_TX_GAS_LIMIT = TRANSACTION_GAS_LIMIT_UPPER_BOUND
MAXIMUM_PENDING_TRANSFERS = 160


class Environment(Enum):
"""Environment configurations that can be chosen on the command line."""
PRODUCTION = 'production'
DEVELOPMENT = 'development'


GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK = 3_234_716
GAS_REQUIRED_PER_SECRET_IN_BATCH = math.ceil(UNLOCK_TX_GAS_LIMIT / MAXIMUM_PENDING_TRANSFERS)
GAS_LIMIT_FOR_TOKEN_CONTRACT_CALL = 100_000
8 changes: 6 additions & 2 deletions raiden/network/proxies/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
from raiden.network.rpc.client import check_address_has_code
from raiden.network.rpc.smartcontract_proxy import ContractProxy
from raiden.network.rpc.transactions import check_transaction_threw
from raiden.utils import pex, privatekey_to_address
from raiden_contracts.constants import CONTRACT_ENDPOINT_REGISTRY
from raiden.utils import pex, privatekey_to_address, safe_gas_limit
from raiden_contracts.constants import (
CONTRACT_ENDPOINT_REGISTRY,
GAS_REQUIRED_FOR_ENDPOINT_REGISTER,
)
from raiden_contracts.contract_manager import ContractManager

log = structlog.get_logger(__name__) # pylint: disable=invalid-name
Expand Down Expand Up @@ -63,6 +66,7 @@ def register_endpoint(self, node_address, endpoint):

transaction_hash = self.proxy.transact(
'registerEndpoint',
safe_gas_limit(GAS_REQUIRED_FOR_ENDPOINT_REGISTER),
endpoint,
)

Expand Down
8 changes: 5 additions & 3 deletions raiden/network/proxies/secret_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from eth_utils import encode_hex, event_abi_to_log_topic, is_binary_address, to_normalized_address
from gevent.event import AsyncResult

from raiden.constants import GENESIS_BLOCK_NUMBER
from raiden.constants import GAS_REQUIRED_PER_SECRET_IN_BATCH, GENESIS_BLOCK_NUMBER
from raiden.exceptions import InvalidAddress, TransactionThrew
from raiden.network.proxies.utils import compare_contract_versions
from raiden.network.rpc.client import StatelessFilter, check_address_has_code
from raiden.network.rpc.transactions import check_transaction_threw
from raiden.utils import pex, privatekey_to_address, sha3, typing
from raiden.utils import pex, privatekey_to_address, safe_gas_limit, sha3, typing
from raiden_contracts.constants import CONTRACT_SECRET_REGISTRY, EVENT_SECRET_REVEALED
from raiden_contracts.contract_manager import ContractManager

Expand Down Expand Up @@ -98,7 +98,9 @@ def register_secret_batch(self, secrets: List[typing.Secret]):
self.open_secret_transactions.pop(secret, None)

def _register_secret_batch(self, secrets):
transaction_hash = self.proxy.transact('registerSecretBatch', secrets)
gas_limit = self.proxy.estimate_gas('registerSecretBatch', secrets)
gas_limit = safe_gas_limit(gas_limit, len(secrets) * GAS_REQUIRED_PER_SECRET_IN_BATCH)
transaction_hash = self.proxy.transact('registerSecretBatch', gas_limit, secrets)
self.client.poll(transaction_hash)
receipt_or_none = check_transaction_threw(self.client, transaction_hash)

Expand Down
12 changes: 11 additions & 1 deletion raiden/network/proxies/token.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import structlog
from eth_utils import is_binary_address, to_checksum_address, to_normalized_address

from raiden.constants import GAS_LIMIT_FOR_TOKEN_CONTRACT_CALL
from raiden.exceptions import TransactionThrew
from raiden.network.rpc.client import check_address_has_code
from raiden.network.rpc.smartcontract_proxy import ContractProxy
from raiden.network.rpc.transactions import check_transaction_threw
from raiden.utils import pex, privatekey_to_address
from raiden.utils import pex, privatekey_to_address, safe_gas_limit
from raiden_contracts.constants import CONTRACT_HUMAN_STANDARD_TOKEN
from raiden_contracts.contract_manager import ContractManager

Expand Down Expand Up @@ -58,8 +59,15 @@ def approve(self, allowed_address, allowance):
}
log.debug('approve called', **log_details)

startgas = self.proxy.estimate_gas(
'approve',
to_checksum_address(allowed_address),
allowance,
)

transaction_hash = self.proxy.transact(
'approve',
safe_gas_limit(startgas),
to_checksum_address(allowed_address),
allowance,
)
Expand Down Expand Up @@ -122,8 +130,10 @@ def transfer(self, to_address, amount):
}
log.debug('transfer called', **log_details)

startgas = GAS_LIMIT_FOR_TOKEN_CONTRACT_CALL
transaction_hash = self.proxy.transact(
'transfer',
safe_gas_limit(startgas),
to_checksum_address(to_address),
amount,
)
Expand Down
83 changes: 81 additions & 2 deletions raiden/network/proxies/token_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from gevent.event import AsyncResult
from gevent.lock import RLock, Semaphore

from raiden.constants import GENESIS_BLOCK_NUMBER
from raiden.constants import GENESIS_BLOCK_NUMBER, UNLOCK_TX_GAS_LIMIT
from raiden.exceptions import (
ChannelOutdatedError,
DepositMismatch,
Expand All @@ -30,9 +30,14 @@
from raiden.network.rpc.client import StatelessFilter, check_address_has_code
from raiden.network.rpc.transactions import check_transaction_threw
from raiden.transfer.balance_proof import pack_balance_proof
from raiden.utils import pex, privatekey_to_address, typing
from raiden.utils import pex, privatekey_to_address, safe_gas_limit, typing
from raiden_contracts.constants import (
CONTRACT_TOKEN_NETWORK,
GAS_REQUIRED_FOR_CLOSE_CHANNEL,
GAS_REQUIRED_FOR_OPEN_CHANNEL,
GAS_REQUIRED_FOR_SET_TOTAL_DEPOSIT,
GAS_REQUIRED_FOR_SETTLE_CHANNEL,
GAS_REQUIRED_FOR_UPDATE_BALANCE_PROOF,
ChannelInfoIndex,
ChannelState,
ParticipantInfoIndex,
Expand Down Expand Up @@ -195,8 +200,17 @@ def _new_netting_channel(self, partner: typing.Address, settle_timeout: int):
if self.channel_exists_and_not_settled(self.node_address, partner):
raise DuplicatedChannelError('Channel with given partner address already exists')

gas_limit = self.proxy.estimate_gas(
'openChannel',
self.node_address,
partner,
settle_timeout,
)
gas_limit = safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_OPEN_CHANNEL)

transaction_hash = self.proxy.transact(
'openChannel',
gas_limit,
self.node_address,
partner,
settle_timeout,
Expand Down Expand Up @@ -589,8 +603,18 @@ def set_total_deposit(
# making the second deposit fail.
token.approve(self.address, amount_to_deposit)

gas_limit = self.proxy.estimate_gas(
'setTotalDeposit',
channel_identifier,
self.node_address,
total_deposit,
partner,
)
gas_limit = safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_SET_TOTAL_DEPOSIT)

transaction_hash = self.proxy.transact(
'setTotalDeposit',
gas_limit,
channel_identifier,
self.node_address,
total_deposit,
Expand All @@ -601,6 +625,12 @@ def set_total_deposit(
receipt_or_none = check_transaction_threw(self.client, transaction_hash)

if receipt_or_none:
latest_deposit = self.detail_participant(
channel_identifier,
self.node_address,
partner,
).deposit

if token.allowance(self.node_address, self.address) < amount_to_deposit:
log_msg = (
'setTotalDeposit failed. The allowance is insufficient, '
Expand All @@ -609,6 +639,8 @@ def set_total_deposit(
)
elif token.balance_of(self.node_address) < amount_to_deposit:
log_msg = 'setTotalDeposit failed. The address doesnt have funds'
elif latest_deposit < total_deposit:
log_msg = 'setTotalDeposit failed. The tokens were not transferred'
else:
log_msg = 'setTotalDeposit failed'

Expand Down Expand Up @@ -668,6 +700,7 @@ def close(
with self.channel_operations_lock[partner]:
transaction_hash = self.proxy.transact(
'closeChannel',
safe_gas_limit(GAS_REQUIRED_FOR_CLOSE_CHANNEL),
channel_identifier,
partner,
balance_hash,
Expand Down Expand Up @@ -771,6 +804,7 @@ def update_transfer(

transaction_hash = self.proxy.transact(
'updateNonClosingBalanceProof',
safe_gas_limit(GAS_REQUIRED_FOR_UPDATE_BALANCE_PROOF),
channel_identifier,
partner,
self.node_address,
Expand Down Expand Up @@ -856,8 +890,13 @@ def withdraw(
raise ValueError(msg)

with self.channel_operations_lock[partner]:
# gaslimit below must be defined
raise NotImplementedError('feature temporarily disabled')
Comment thread
palango marked this conversation as resolved.

gas_limit = None
transaction_hash = self.proxy.transact(
'setTotalWithdraw',
gas_limit,
channel_identifier,
self.node_address,
total_withdraw,
Expand Down Expand Up @@ -902,8 +941,18 @@ def unlock(

leaves_packed = b''.join(lock.encoded for lock in merkle_tree_leaves)

gas_limit = self.proxy.estimate_gas(
'unlock',
channel_identifier,
self.node_address,
partner,
leaves_packed,
)
gas_limit = safe_gas_limit(gas_limit, UNLOCK_TX_GAS_LIMIT)

transaction_hash = self.proxy.transact(
'unlock',
gas_limit,
channel_identifier,
self.node_address,
partner,
Expand Down Expand Up @@ -974,8 +1023,23 @@ def settle(
our_bp_is_larger = our_maximum > partner_maximum

if our_bp_is_larger:
gas_limit = self.proxy.estimate_gas(
'settleChannel',
channel_identifier,
partner,
partner_transferred_amount,
partner_locked_amount,
partner_locksroot,
self.node_address,
transferred_amount,
locked_amount,
locksroot,
)
gas_limit = safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_SETTLE_CHANNEL)

transaction_hash = self.proxy.transact(
'settleChannel',
gas_limit,
channel_identifier,
partner,
partner_transferred_amount,
Expand All @@ -987,8 +1051,23 @@ def settle(
locksroot,
)
else:
gas_limit = self.proxy.estimate_gas(
'settleChannel',
channel_identifier,
self.node_address,
transferred_amount,
locked_amount,
locksroot,
partner,
partner_transferred_amount,
partner_locked_amount,
partner_locksroot,
)
gas_limit = safe_gas_limit(gas_limit, GAS_REQUIRED_FOR_SETTLE_CHANNEL)

transaction_hash = self.proxy.transact(
'settleChannel',
gas_limit,
channel_identifier,
self.node_address,
transferred_amount,
Expand Down
9 changes: 7 additions & 2 deletions raiden/network/proxies/token_network_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
to_normalized_address,
)

from raiden.constants import GENESIS_BLOCK_NUMBER, NULL_ADDRESS
from raiden.constants import (
GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK,
GENESIS_BLOCK_NUMBER,
NULL_ADDRESS,
)
from raiden.exceptions import InvalidAddress, RaidenRecoverableError, TransactionThrew
from raiden.network.proxies.utils import compare_contract_versions
from raiden.network.rpc.client import StatelessFilter, check_address_has_code
from raiden.network.rpc.transactions import check_transaction_threw
from raiden.utils import pex, privatekey_to_address, typing
from raiden.utils import pex, privatekey_to_address, safe_gas_limit, typing
from raiden_contracts.constants import CONTRACT_TOKEN_NETWORK_REGISTRY, EVENT_TOKEN_NETWORK_CREATED
from raiden_contracts.contract_manager import ContractManager

Expand Down Expand Up @@ -83,6 +87,7 @@ def add_token(self, token_address: typing.TokenAddress):

transaction_hash = self.proxy.transact(
'createERC20TokenNetwork',
safe_gas_limit(GAS_REQUIRED_FOR_CREATE_ERC20_TOKEN_NETWORK),
token_address,
)

Expand Down
14 changes: 3 additions & 11 deletions raiden/network/rpc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,22 +257,13 @@ def balance(self, account: typing.Address):
""" Return the balance of the account of given address. """
return self.web3.eth.getBalance(to_checksum_address(account), 'pending')

def gaslimit(self, location='latest') -> int:
gas_limit = self.web3.eth.getBlock(location)['gasLimit']
return gas_limit * 8 // 10

def gas_price(self) -> int:
# generateGasPrice takes the transaction to be send as an optional argument
# but both strategies that we are using (time-based and rpc-based) don't make
# use of this argument. It is therefore safe to not provide it at the moment.
# This needs to be reevaluated if we use different gas price strategies
return int(self.web3.eth.generateGasPrice())

def check_startgas(self, startgas):
if not startgas:
return self.gaslimit()
return startgas

def new_contract_proxy(self, contract_interface, contract_address: typing.Address):
""" Return a proxy for interacting with a smart contract.

Expand Down Expand Up @@ -368,8 +359,10 @@ def deploy_solidity_contract(

dependency_contract['bin'] = bytecode

gas_limit = self.web3.eth.getBlock('latest')['gasLimit'] * 8 // 10
transaction_hash = self.send_transaction(
to=typing.Address(b''),
startgas=gas_limit,
data=bytecode,
)

Expand Down Expand Up @@ -427,9 +420,9 @@ def deploy_solidity_contract(
def send_transaction(
self,
to: typing.Address,
startgas: int,
value: int = 0,
data: bytes = b'',
startgas: int = None,
) -> bytes:
""" Helper to send signed messages.

Expand All @@ -442,7 +435,6 @@ def send_transaction(

with self._nonce_lock:
nonce = self._available_nonce
startgas = self.check_startgas(startgas)
gas_price = self.gas_price()

transaction = {
Expand Down
3 changes: 2 additions & 1 deletion raiden/network/rpc/smartcontract_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ def __init__(
self.jsonrpc_client = jsonrpc_client
self.contract = contract

def transact(self, function_name: str, *args, **kargs):
def transact(self, function_name: str, startgas: int, *args, **kargs):
data = ContractProxy.get_transaction_data(self.contract.abi, function_name, args)

try:
txhash = self.jsonrpc_client.send_transaction(
to=self.contract.address,
startgas=startgas,
value=kargs.pop('value', 0),
data=decode_hex(data),
**kargs,
Expand Down
Loading