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
21 changes: 13 additions & 8 deletions crates/astria-sequencer/src/accounts/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ use crate::{
transaction::action_handler::ActionHandler,
};

/// Fee charged for a `Transfer` action.
pub(crate) const TRANSFER_FEE: u128 = 12;

pub(crate) async fn transfer_check_stateful<S: StateReadExt + 'static>(
action: &TransferAction,
state: &S,
Expand All @@ -38,6 +35,10 @@ pub(crate) async fn transfer_check_stateful<S: StateReadExt + 'static>(
"invalid fee asset",
);

let fee = state
.get_transfer_base_fee()
.await
.context("failed to get transfer base fee")?;
let transfer_asset_id = action.asset_id;

let from_fee_balance = state
Expand All @@ -50,7 +51,7 @@ pub(crate) async fn transfer_check_stateful<S: StateReadExt + 'static>(
if action.fee_asset_id == transfer_asset_id {
let payment_amount = action
.amount
.checked_add(TRANSFER_FEE)
.checked_add(fee)
.context("transfer amount plus fee overflowed")?;

ensure!(
Expand All @@ -61,7 +62,7 @@ pub(crate) async fn transfer_check_stateful<S: StateReadExt + 'static>(
// otherwise, check the fee asset account has enough to cover the fees,
// and the transfer asset account has enough to cover the transfer
ensure!(
from_fee_balance >= TRANSFER_FEE,
from_fee_balance >= fee,
"insufficient funds for fee payment"
);

Expand Down Expand Up @@ -109,8 +110,12 @@ impl ActionHandler for TransferAction {
)
)]
async fn execute<S: StateWriteExt>(&self, state: &mut S, from: Address) -> Result<()> {
let fee = state
.get_transfer_base_fee()
.await
.context("failed to get transfer base fee")?;
state
.get_and_increase_block_fees(self.fee_asset_id, TRANSFER_FEE)
.get_and_increase_block_fees(self.fee_asset_id, fee)
.await
.context("failed to add to block fees")?;

Expand All @@ -122,7 +127,7 @@ impl ActionHandler for TransferAction {
// check_stateful should have already checked this arithmetic
let payment_amount = self
.amount
.checked_add(TRANSFER_FEE)
.checked_add(fee)
.expect("transfer amount plus fee should not overflow");

state
Expand All @@ -147,7 +152,7 @@ impl ActionHandler for TransferAction {

// deduct fee from fee asset balance
state
.decrease_balance(from, self.fee_asset_id, TRANSFER_FEE)
.decrease_balance(from, self.fee_asset_id, fee)
.await
.context("failed decreasing `from` account balance for fee payment")?;
}
Expand Down
7 changes: 7 additions & 0 deletions crates/astria-sequencer/src/accounts/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ use crate::{
genesis::GenesisState,
};

/// Default transfer base fee.
/// TODO: put in app genesis state
pub(crate) const DEFAULT_TRANSFER_BASE_FEE: u128 = 12;

#[derive(Default)]
pub(crate) struct AccountsComponent;

Expand All @@ -33,6 +37,9 @@ impl Component for AccountsComponent {
.context("failed writing account balance to state")?;
}

state
.put_transfer_base_fee(DEFAULT_TRANSFER_BASE_FEE)
.context("failed to put transfer base fee")?;
Ok(())
}

Expand Down
26 changes: 26 additions & 0 deletions crates/astria-sequencer/src/accounts/state_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ struct Nonce(u32);
#[derive(BorshSerialize, BorshDeserialize, Debug)]
struct Balance(u128);

/// Newtype wrapper to read and write a u128 from rocksdb.
#[derive(BorshSerialize, BorshDeserialize, Debug)]
struct Fee(u128);

const ACCOUNTS_PREFIX: &str = "accounts";
const TRANSFER_BASE_FEE_STORAGE_KEY: &str = "transferfee";

fn storage_key(address: &str) -> String {
format!("{ACCOUNTS_PREFIX}/{address}")
Expand Down Expand Up @@ -127,6 +132,20 @@ pub(crate) trait StateReadExt: StateRead {
let Nonce(nonce) = Nonce::try_from_slice(&bytes).context("invalid nonce bytes")?;
Ok(nonce)
}

#[instrument(skip_all)]
async fn get_transfer_base_fee(&self) -> Result<u128> {
let bytes = self
.get_raw(TRANSFER_BASE_FEE_STORAGE_KEY)
.await
.context("failed reading raw transfer base fee from state")?;
let Some(bytes) = bytes else {
return Err(anyhow::anyhow!("transfer base fee not set"));
};

let Fee(fee) = Fee::try_from_slice(&bytes).context("invalid fee bytes")?;
Ok(fee)
}
}

impl<T: StateRead + ?Sized> StateReadExt for T {}
Expand Down Expand Up @@ -195,6 +214,13 @@ pub(crate) trait StateWriteExt: StateWrite {
.context("failed to store updated account balance in database")?;
Ok(())
}

#[instrument(skip(self))]
fn put_transfer_base_fee(&mut self, fee: u128) -> Result<()> {
let bytes = borsh::to_vec(&Fee(fee)).context("failed to serialize fee")?;
self.put_raw(TRANSFER_BASE_FEE_STORAGE_KEY.to_string(), bytes);
Ok(())
}
}

impl<T: StateWrite> StateWriteExt for T {}
Expand Down
86 changes: 64 additions & 22 deletions crates/astria-sequencer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,12 @@ use crate::{
StateWriteExt as _,
},
},
bridge::state_ext::{
StateReadExt as _,
StateWriteExt,
bridge::{
component::BridgeComponent,
state_ext::{
StateReadExt as _,
StateWriteExt,
},
},
component::Component as _,
genesis::GenesisState,
Expand All @@ -84,6 +87,7 @@ use crate::{
GeneratedCommitments,
},
},
sequence::component::SequenceComponent,
state_ext::{
StateReadExt as _,
StateWriteExt as _,
Expand Down Expand Up @@ -198,9 +202,15 @@ impl App {
)
.await
.context("failed to call init_chain on AuthorityComponent")?;
BridgeComponent::init_chain(&mut state_tx, &genesis_state)
.await
.context("failed to call init_chain on BridgeComponent")?;
IbcComponent::init_chain(&mut state_tx, &genesis_state)
.await
.context("failed to call init_chain on IbcComponent")?;
SequenceComponent::init_chain(&mut state_tx, &genesis_state)
.await
.context("failed to call init_chain on SequenceComponent")?;

state_tx.apply();

Expand Down Expand Up @@ -797,9 +807,15 @@ impl App {
AuthorityComponent::begin_block(&mut arc_state_tx, begin_block)
.await
.context("failed to call begin_block on AuthorityComponent")?;
BridgeComponent::begin_block(&mut arc_state_tx, begin_block)
.await
.context("failed to call begin_block on BridgeComponent")?;
IbcComponent::begin_block(&mut arc_state_tx, begin_block)
.await
.context("failed to call begin_block on IbcComponent")?;
SequenceComponent::begin_block(&mut arc_state_tx, begin_block)
.await
.context("failed to call begin_block on SequenceComponent")?;

let state_tx = Arc::try_unwrap(arc_state_tx)
.expect("components should not retain copies of shared state");
Expand Down Expand Up @@ -870,9 +886,15 @@ impl App {
AuthorityComponent::end_block(&mut arc_state_tx, &end_block)
.await
.context("failed to call end_block on AuthorityComponent")?;
BridgeComponent::end_block(&mut arc_state_tx, &end_block)
.await
.context("failed to call end_block on BridgeComponent")?;
IbcComponent::end_block(&mut arc_state_tx, &end_block)
.await
.context("failed to call end_block on IbcComponent")?;
SequenceComponent::end_block(&mut arc_state_tx, &end_block)
.await
.context("failed to call end_block on SequenceComponent")?;

let mut state_tx = Arc::try_unwrap(arc_state_tx)
.expect("components should not retain copies of shared state");
Expand Down Expand Up @@ -1057,13 +1079,12 @@ mod test {

use super::*;
use crate::{
accounts::action::TRANSFER_FEE,
app::test_utils::*,
asset::get_native_asset,
authority::state_ext::ValidatorSet,
genesis::Account,
ibc::state_ext::StateReadExt as _,
sequence::calculate_fee,
sequence::calculate_fee_from_state,
transaction::InvalidChainId,
};

Expand Down Expand Up @@ -1289,12 +1310,13 @@ mod test {
.unwrap(),
value + 10u128.pow(19)
);
let transfer_fee = app.state.get_transfer_base_fee().await.unwrap();
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

);
assert_eq!(app.state.get_account_nonce(bob_address).await.unwrap(), 0);
assert_eq!(app.state.get_account_nonce(alice_address).await.unwrap(), 1);
Expand Down Expand Up @@ -1353,12 +1375,13 @@ mod test {
value, // transferred amount
);

let transfer_fee = app.state.get_transfer_base_fee().await.unwrap();
assert_eq!(
app.state
.get_account_balance(alice_address, native_asset)
.await
.unwrap(),
10u128.pow(19) - TRANSFER_FEE, // genesis balance - fee
10u128.pow(19) - transfer_fee, // genesis balance - fee
);
assert_eq!(
app.state
Expand Down Expand Up @@ -1422,7 +1445,9 @@ mod test {

// figure out needed fee for a single transfer
let data = b"hello world".to_vec();
let fee = calculate_fee(&data).unwrap();
let fee = calculate_fee_from_state(&data, &app.state.clone())
.await
.unwrap();

// transfer just enough to cover single sequence fee with data
let signed_tx = UnsignedTransaction {
Expand Down Expand Up @@ -1500,11 +1525,17 @@ mod test {

#[tokio::test]
async fn app_execute_transaction_sequence() {
use crate::sequence::state_ext::StateWriteExt as _;

let mut app = initialize_app(None, vec![]).await;
let mut state_tx = StateDelta::new(app.state.clone());
state_tx.put_sequence_action_base_fee(0);
state_tx.put_sequence_action_byte_cost_multiplier(1);
app.apply(state_tx);

let (alice_signing_key, alice_address) = get_alice_signing_key_and_address();
let data = b"hello world".to_vec();
let fee = calculate_fee(&data).unwrap();
let fee = calculate_fee_from_state(&data, &app.state).await.unwrap();

let tx = UnsignedTransaction {
params: TransactionParams {
Expand Down Expand Up @@ -1876,10 +1907,12 @@ mod test {
async fn app_execute_transaction_init_bridge_account_ok() {
use astria_core::protocol::transaction::v1alpha1::action::InitBridgeAccountAction;

use crate::bridge::init_bridge_account_action::INIT_BRIDGE_ACCOUNT_FEE;

let (alice_signing_key, alice_address) = get_alice_signing_key_and_address();
let mut app = initialize_app(None, vec![]).await;
let mut state_tx = StateDelta::new(app.state.clone());
let fee = 12; // arbitrary
state_tx.put_init_bridge_account_base_fee(fee);
app.apply(state_tx);

let rollup_id = RollupId::from_unhashed_bytes(b"testchainid");
let asset_id = get_native_asset().id();
Expand Down Expand Up @@ -1925,7 +1958,7 @@ mod test {
.get_account_balance(alice_address, asset_id)
.await
.unwrap(),
before_balance - INIT_BRIDGE_ACCOUNT_FEE
before_balance - fee,
);
}

Expand Down Expand Up @@ -2018,12 +2051,28 @@ mod test {

app.execute_transaction(signed_tx).await.unwrap();
assert_eq!(app.state.get_account_nonce(alice_address).await.unwrap(), 1);
let transfer_fee = app.state.get_transfer_base_fee().await.unwrap();
let expected_deposit = Deposit::new(
bridge_address,
rollup_id,
amount,
asset_id,
"nootwashere".to_string(),
);

let fee = transfer_fee
+ app
.state
.get_bridge_lock_byte_cost_multiplier()
.await
.unwrap()
* crate::bridge::get_deposit_byte_len(&expected_deposit);
assert_eq!(
app.state
.get_account_balance(alice_address, asset_id)
.await
.unwrap(),
alice_before_balance - (amount + TRANSFER_FEE)
alice_before_balance - (amount + fee)
);
assert_eq!(
app.state
Expand All @@ -2033,14 +2082,6 @@ mod test {
bridge_before_balance + amount
);

let expected_deposit = Deposit::new(
bridge_address,
rollup_id,
amount,
asset_id,
"nootwashere".to_string(),
);

let deposits = app.state.get_deposit_events(&rollup_id).await.unwrap();
assert_eq!(deposits.len(), 1);
assert_eq!(deposits[0], expected_deposit);
Expand Down Expand Up @@ -2417,12 +2458,13 @@ mod test {
app.commit(storage).await;

// assert that transaction fees were transferred to the block proposer
let transfer_fee = app.state.get_transfer_base_fee().await.unwrap();
assert_eq!(
app.state
.get_account_balance(sequencer_proposer_address, native_asset)
.await
.unwrap(),
TRANSFER_FEE,
transfer_fee,
);
assert_eq!(app.state.get_block_fees().await.unwrap().len(), 0);
}
Expand Down
Loading
Loading