-
Notifications
You must be signed in to change notification settings - Fork 88
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
refactor(sequencer): store fees for actions in app state #1017
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requesting changes due to the use of std::mem::size_of
. The rest looks fine to me, but I would like have saturating arithmetic instead of bare +
and *
. Also, unit tests for the following would be great:
- all fee calculations
- the byte length of
Deposit
- writing/reading from rocksdb
Orthogonal to this PR I have another concern:
We have no mechanism that ensure that the keys in hardcoded in the various state_ext
modules/StateExt
immplementations don't clash (scenario: somebody wants to store a new object in the DB 6 months from now). I think it might make sense to list all of them in a central locations in a first step (astria_sequencer::database_keys
maybe?). In a second step I want to think about how we can ensure at compile time that they don't clash.
@@ -95,11 +104,12 @@ pub(crate) async fn check_balance_for_total_fees<S: StateReadExt + 'static>( | |||
.or_insert(act.amount); | |||
fees_by_asset | |||
.entry(act.fee_asset_id) | |||
.and_modify(|amt| *amt += TRANSFER_FEE) | |||
.or_insert(TRANSFER_FEE); | |||
.and_modify(|amt| *amt += transfer_fee) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.and_modify(|amt| *amt += transfer_fee) | |
.and_modify(|amt| *amt = amt.saturating_add(transfer_fee)) |
@@ -113,14 +123,14 @@ pub(crate) async fn check_balance_for_total_fees<S: StateReadExt + 'static>( | |||
.or_insert(act.amount()); | |||
fees_by_asset | |||
.entry(*act.fee_asset_id()) | |||
.and_modify(|amt| *amt += ICS20_WITHDRAWAL_FEE) | |||
.or_insert(ICS20_WITHDRAWAL_FEE); | |||
.and_modify(|amt| *amt += ics20_withdrawal_fee) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.and_modify(|amt| *amt += ics20_withdrawal_fee) | |
.and_modify(|amt| *amt = amt.saturating_add(ics20_withdrawal_fee)) |
} | ||
Action::InitBridgeAccount(act) => { | ||
fees_by_asset | ||
.entry(act.fee_asset_id) | ||
.and_modify(|amt| *amt += INIT_BRIDGE_ACCOUNT_FEE) | ||
.or_insert(INIT_BRIDGE_ACCOUNT_FEE); | ||
.and_modify(|amt| *amt += init_bridge_account_fee) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.and_modify(|amt| *amt += init_bridge_account_fee) | |
.and_modify(|amt| *amt = amt.saturating_add(init_bridge_account_fee)) |
@@ -129,8 +139,8 @@ pub(crate) async fn check_balance_for_total_fees<S: StateReadExt + 'static>( | |||
.or_insert(act.amount); | |||
fees_by_asset | |||
.entry(act.fee_asset_id) | |||
.and_modify(|amt| *amt += TRANSFER_FEE) | |||
.or_insert(TRANSFER_FEE); | |||
.and_modify(|amt| *amt += transfer_fee) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.and_modify(|amt| *amt += transfer_fee) | |
.and_modify(|amt| *amt = amt.saturating_add(transfer_fee)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add simple unit tests to this file in line with the other unit tests that were added over the last few weeks?
.get_bridge_lock_byte_cost_multiplier() | ||
.await | ||
.context("failed to get byte cost multiplier")?; | ||
let fee = byte_cost_multiplier * DEPOSIT_BYTE_LEN + transfer_fee; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Saturating arithmetic:
let fee = byte_cost_multiplier.saturating_mul(DEPOSIT_BYTE_LEN).saturating_add(transfer_fee);
Also, please add a unit tests for this calculation.
@@ -26,6 +32,8 @@ use crate::{ | |||
transaction::action_handler::ActionHandler, | |||
}; | |||
|
|||
pub(crate) const DEPOSIT_BYTE_LEN: u128 = std::mem::size_of::<Deposit>() as u128; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use the Rust in-memory representation of types if you want deterministic results. There are no guarantees on ordering of fields or alignment paddding. You can get different results depending on Rust compiler or even architecture.
You can use #[repr(C)]
to get a defined layout (and hence deterministic size), but even that is tricky if somebody changes the representation of one of Deposit
's fields.
For Deposit
in partciular I notice that it contains destination_chain_address: String
, the memory size of which is is 24 bytes on a 64bit host (64 bit pointer, 64 bit capacity and length), with the actual memory used being unbounded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point on the destination_chain_address
, i'll change this to calculate the byte size at runtime (similar to sequence actions)
.get_bridge_lock_byte_cost_multiplier() | ||
.await | ||
.context("failed to get byte cost multiplier")?; | ||
let fee = byte_cost_multiplier * DEPOSIT_BYTE_LEN; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let fee = byte_cost_multiplier * DEPOSIT_BYTE_LEN; | |
let fee = byte_cost_multiplier.saturating_mul(DEPOSIT_BYTE_LEN); |
And unit test please.
.decrease_balance(from, self.fee_asset_id, fee) | ||
.await | ||
.context("failed to deduct fee from account balance")?; | ||
|
||
let rollup_id = state | ||
.get_bridge_account_rollup_id(&self.to) | ||
.await? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed the bare question mark (outside of the PR, but can you context please?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. Just once place I found that should get error context.
assert_eq!( | ||
app.state | ||
.get_account_balance(alice_address, native_asset) | ||
.await | ||
.unwrap(), | ||
10u128.pow(19) - (value + TRANSFER_FEE), | ||
10u128.pow(19) - (value + transfer_fee), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should really move this tests
module into src/app/tests.rs
. I wanted to comment several times now how I want saturating arithmetic. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i was planning to move it next, it's getting way too long lol
@@ -41,6 +38,8 @@ impl ActionHandler for InitBridgeAccountAction { | |||
"invalid fee asset", | |||
); | |||
|
|||
let fee = state.get_init_bridge_account_base_fee().await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context?
Summary
store fees for actions in app state, fees are of the form
base_fee + byte_cost_multiplier * byte_size
.for txs that don't write to DA, they have an implicit
byte_size
of zero, so they only have a base fee.for txs that write to DA, they have a
byte_cost_multiplier
which is multiplied by the number of bytes written to DA for fee calculation.Background
this is so eventually we can change fees via a tx.
Changes
Testing
unit tests
Related Issues
part of #1003