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

refactor(sequencer): store fees for actions in app state #1017

Merged
merged 14 commits into from
Apr 30, 2024
Merged

Conversation

noot
Copy link
Collaborator

@noot noot commented Apr 26, 2024

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

  • store fees for actions in app state

Testing

unit tests

Related Issues

part of #1003

@noot noot requested a review from a team as a code owner April 26, 2024 20:56
@noot noot requested a review from SuperFluffy April 26, 2024 20:57
@github-actions github-actions bot added the sequencer pertaining to the astria-sequencer crate label Apr 26, 2024
Copy link
Member

@SuperFluffy SuperFluffy left a 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:

  1. all fee calculations
  2. the byte length of Deposit
  3. 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.and_modify(|amt| *amt += transfer_fee)
.and_modify(|amt| *amt = amt.saturating_add(transfer_fee))

Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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.

Copy link
Collaborator Author

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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?
Copy link
Member

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?)

@noot noot requested review from SuperFluffy and Lilyjjo April 29, 2024 19:13
Copy link
Member

@SuperFluffy SuperFluffy left a 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),
Copy link
Member

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. :-)

Copy link
Collaborator Author

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?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context?

@noot noot enabled auto-merge April 30, 2024 17:32
@noot noot added this pull request to the merge queue Apr 30, 2024
Merged via the queue into main with commit 049ec61 Apr 30, 2024
36 checks passed
@noot noot deleted the noot/sequencer-fees branch April 30, 2024 17:43
joroshiba added a commit that referenced this pull request May 2, 2024
Reverts #1032

#1017 was incorrectly labelled
as non breaking.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sequencer pertaining to the astria-sequencer crate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants