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

Better fee calculation #370

Merged
merged 28 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2376d5d
Add better support for txn flags
LimpidCrypto Mar 15, 2022
973dfbf
support Transaction.has_flag()
LimpidCrypto Mar 15, 2022
8533884
support Transaction.has_flag()
LimpidCrypto Mar 15, 2022
0bfb37d
Add better support for txn flags.
LimpidCrypto Mar 17, 2022
3c1c1a2
bump version; fix import TypedDict
LimpidCrypto Mar 18, 2022
4dc7ed5
Update CHANGELOG.md
LimpidCrypto Mar 18, 2022
b148cf8
All flags to upper case; Update Changelog; Update pyproject
LimpidCrypto Mar 21, 2022
aa9bfa1
All flags to upper case; Update Changelog; Update pyproject
LimpidCrypto Mar 21, 2022
a958b0b
Merge branch 'master' into master
LimpidCrypto Mar 21, 2022
6053562
Merge branch 'XRPLF:master' into master
LimpidCrypto Mar 29, 2022
abc18d8
Merge branch 'XRPLF:master' into master
LimpidCrypto Apr 5, 2022
34cda4d
better fee calculation
LimpidCrypto Apr 5, 2022
3e5804a
Edit docstring; Add inline-comments
LimpidCrypto Apr 5, 2022
a125a75
Fix typo
LimpidCrypto Apr 5, 2022
d2bea47
Add referral to original formula
LimpidCrypto Apr 5, 2022
5cae934
Update settings.json
LimpidCrypto Apr 5, 2022
5a6b90f
add helper function; add tests
LimpidCrypto Apr 6, 2022
2479ac6
Merge branch 'master' into better_fee_calculation
LimpidCrypto Apr 6, 2022
dac32d3
fix linter; add option dynamic; change default back to open
LimpidCrypto Apr 6, 2022
5108588
fix exception message
LimpidCrypto Apr 7, 2022
76302a4
Edit docstrings; Remove
LimpidCrypto Apr 11, 2022
7934511
Add comments for calculations
LimpidCrypto Apr 11, 2022
fa0eb25
Merge branch 'master' into better_fee_calculation
LimpidCrypto Apr 15, 2022
6ccce1d
Update CHANGELOG.md
LimpidCrypto Apr 16, 2022
c07c4c3
Merge branch 'master' into better_fee_calculation
LimpidCrypto Apr 22, 2022
008869c
Merge branch 'master' into better_fee_calculation
LimpidCrypto Apr 25, 2022
72febec
Update CHANGELOG.md
LimpidCrypto Apr 25, 2022
151d06b
Update CHANGELOG.md
LimpidCrypto Apr 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.enabled": true,
"restructuredtext.confPath": "${workspaceFolder}/docs"
"restructuredtext.confPath": "${workspaceFolder}/docs",
"esbonio.sphinx.confDir": "${workspaceFolder}/docs"
}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support setting flags with booleans. For each transaction type supporting flags there is a `FlagInterface` to set the flags with booleans.
- `federator_info` RPC support
- Helper method for creating a cross-chain payment to/from a sidechain
- Support for dynamic fee calculation
LimpidCrypto marked this conversation as resolved.
Show resolved Hide resolved

LimpidCrypto marked this conversation as resolved.
Show resolved Hide resolved
### Fixed
- `xrpl.asyncio.clients` exports (now includes `request_to_websocket`, `websocket_to_response`)
Expand Down
Empty file.
74 changes: 74 additions & 0 deletions tests/unit/asyn/ledger/test_calculate_fee_dynamically.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from unittest import TestCase

from xrpl.asyncio.ledger.main import calculate_fee_dynamically


class TestCalculateFeeDynamically(TestCase):
def test_queue_empty(self):
actual = {
"current_ledger_size": "46",
"current_queue_size": "0",
"drops": {
"base_fee": "10",
"median_fee": "5000",
"minimum_fee": "10",
"open_ledger_fee": "10",
},
"expected_ledger_size": "176",
"ledger_current_index": 70813866,
"levels": {
"median_level": "128000",
"minimum_level": "256",
"open_ledger_level": "256",
"reference_level": "256",
},
"max_queue_size": "3520",
}
expected = "15"
self.assertEqual(calculate_fee_dynamically(fee_data_set=actual), expected)

def test_queue_partially_filled(self):
actual = {
"current_ledger_size": "46",
"current_queue_size": "1760",
"drops": {
"base_fee": "10",
"median_fee": "5000",
"minimum_fee": "10",
"open_ledger_fee": "10",
},
"expected_ledger_size": "176",
"ledger_current_index": 70813866,
"levels": {
"median_level": "128000",
"minimum_level": "256",
"open_ledger_level": "256",
"reference_level": "256",
},
"max_queue_size": "3520",
}
expected = "225"
self.assertEqual(calculate_fee_dynamically(fee_data_set=actual), expected)

def test_queue_full(self):
actual = {
"current_ledger_size": "46",
"current_queue_size": "3520",
"drops": {
"base_fee": "10",
"median_fee": "5000",
"minimum_fee": "10",
"open_ledger_fee": "10",
},
"expected_ledger_size": "176",
"ledger_current_index": 70813866,
"levels": {
"median_level": "128000",
"minimum_level": "256",
"open_ledger_level": "256",
"reference_level": "256",
},
"max_queue_size": "3520",
}
expected = "5500"
self.assertEqual(calculate_fee_dynamically(fee_data_set=actual), expected)
21 changes: 14 additions & 7 deletions xrpl/asyncio/ledger/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Optional, cast

from xrpl.asyncio.clients import Client, XRPLRequestFailureException
from xrpl.asyncio.ledger.utils import calculate_fee_dynamically
from xrpl.constants import XRPLException
from xrpl.models.requests import Fee, Ledger
from xrpl.utils import xrp_to_drops
Expand Down Expand Up @@ -60,11 +61,14 @@ async def get_fee(
high, then the fees will not scale past the maximum fee. If None, there is
no ceiling for the fee. The default is 2 XRP.
fee_type: The type of fee to return. The options are "open" (the load-scaled
fee to get into the open ledger) or "minimum" (the minimum transaction
fee). The default is "open".
fee to get into the open ledger), "minimum" (the minimum transaction
fee) or "dynamic" (dynamic fee-calculation based on the queue size
of the node). The default is "open". The recommended option is
"dynamic".

Returns:
The transaction fee, in drops.
`Read more about drops <https://xrpl.org/currency-formats.html#xrp-amounts>`_
LimpidCrypto marked this conversation as resolved.
Show resolved Hide resolved

Raises:
XRPLException: if an incorrect option for `fee_type` is passed in.
Expand All @@ -74,15 +78,18 @@ async def get_fee(
if not response.is_successful():
raise XRPLRequestFailureException(response.result)

result = response.result["drops"]
result = response.result
drops = result["drops"]
if fee_type == "open":
fee = cast(str, result["open_ledger_fee"])
fee = cast(str, drops["open_ledger_fee"])
elif fee_type == "minimum":
fee = cast(str, result["minimum_fee"])
fee = cast(str, drops["minimum_fee"])
elif fee_type == "dynamic":
fee = calculate_fee_dynamically(fee_data_set=result)
else:
raise XRPLException(
f'`fee_type` param must be "open" or "minimum". {fee_type} is not a '
"valid option."
'`fee_type` param must be "open", "minimum" or "dynamic".'
f" {fee_type} is not a valid option."
)
if max_fee is not None:
max_fee_drops = int(xrp_to_drops(max_fee))
Expand Down
69 changes: 69 additions & 0 deletions xrpl/asyncio/ledger/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Helper functions for the ledger module."""

from typing import Any, Dict


def calculate_fee_dynamically(fee_data_set: Dict[str, Any]) -> str:
"""Calculate the transaction fee dynamically
based on the size of the queue of the node.

Args:
fee_data_set (Dict[str, Any]): The result of the `fee` method.

Returns:
str: The transaction fee, in drops.
`Read more about drops <https://xrpl.org/currency-formats.html#xrp-amounts>`_

Based on fee-calculation code here:
`<https://gist.github.com/WietseWind/3e9f9339f37a5881978a9661f49b0e52>`_
"""
current_queue_size = int(fee_data_set["current_queue_size"])
max_queue_size = int(fee_data_set["max_queue_size"])
queue_pct = current_queue_size / max_queue_size
drops = fee_data_set["drops"]
minimum_fee = int(drops["minimum_fee"])
median_fee = int(drops["median_fee"])
open_ledger_fee = int(drops["open_ledger_fee"])

fee_low = round(
LimpidCrypto marked this conversation as resolved.
Show resolved Hide resolved
min(
max(minimum_fee * 1.5, round(max(median_fee, open_ledger_fee) / 500)),
1000,
),
)
if queue_pct > 0.1: # if 'current_queue_size' is >10 % of 'max_queue_size'
possible_fee_medium = round(
(minimum_fee + median_fee + open_ledger_fee) / 3,
)
elif queue_pct == 0: # if 'current_queue_size' is 0
possible_fee_medium = max(
10 * minimum_fee,
open_ledger_fee,
)
else:
possible_fee_medium = max(
10 * minimum_fee,
round((minimum_fee + median_fee) / 2),
)
fee_medium = round(
min(
possible_fee_medium,
fee_low * 15,
10000,
),
)
fee_high = round(
min(
max(10 * minimum_fee, round(max(median_fee, open_ledger_fee) * 1.1)),
100000,
),
)
LimpidCrypto marked this conversation as resolved.
Show resolved Hide resolved

if queue_pct == 0: # if queue is empty
fee = str(fee_low)
elif 0 < queue_pct < 1: # queue has txns in it but is not full
fee = str(fee_medium)
else: # if queue is full
fee = str(fee_high)

return fee
17 changes: 12 additions & 5 deletions xrpl/ledger/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,31 @@ def get_latest_open_ledger_sequence(client: SyncClient) -> int:


def get_fee(
client: SyncClient, *, max_fee: Optional[float] = 2, fee_type: str = "open"
client: SyncClient,
*,
max_fee: Optional[float] = 2,
fee_type: str = "open",
) -> str:
"""
Query the ledger for the current minimum transaction fee.
Query the ledger for the current transaction fee.

Args:
client: the network client used to make network calls.
max_fee: The maximum fee in XRP that the user wants to pay. If load gets too
high, then the fees will not scale past the maximum fee. If None, there is
no ceiling for the fee. The default is 2 XRP.
fee_type: The type of fee to return. The options are "open" (the load-scaled
fee to get into the open ledger) or "minimum" (the minimum transaction
cost). The default is "open".
fee to get into the open ledger), "minimum" (the minimum transaction
fee) or "dynamic" (dynamic fee-calculation based on the queue size
of the node). The default is "open". The recommended option is
"dynamic".

Returns:
The minimum fee for transactions.
The transaction fee, in drops.
`Read more about drops <https://xrpl.org/currency-formats.html#xrp-amounts>`_

Raises:
XRPLException: if an incorrect option for `fee_type` is passed in.
XRPLRequestFailureException: if the rippled API call fails.
"""
return asyncio.run(main.get_fee(client, max_fee=max_fee, fee_type=fee_type))