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

use cost model to limit new account creation #21369

Merged
merged 8 commits into from
Dec 12, 2021

Conversation

jeffwashington
Copy link
Contributor

Problem

Summary of Changes

Fixes #

space,
owner: _owner,
} => space,
_ => 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we also need to consider SystemInstruction::Allocate{,WithSeed} here, otherwise the checks can be sidestepped with a discrete Allocate -> Assign -> Transfer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added these 2 instructions.

Copy link
Contributor

Choose a reason for hiding this comment

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

@jackcmay what about the realloc instruction?

Copy link
Contributor

Choose a reason for hiding this comment

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

Realloc is not an instruction, it is an operation that a program can do on accounts they own, from within the program:

pub fn realloc(&self, new_len: usize, zero_init: bool) -> Result<(), ProgramError> {

Copy link
Contributor

Choose a reason for hiding this comment

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

Realloc is not an instruction, it is an operation that a program can do on accounts they own, from within the program:

pub fn realloc(&self, new_len: usize, zero_init: bool) -> Result<(), ProgramError> {

It doesn't seem like there's any runtime accounting of this operation? It'd be nice to consider its contributions to storage changes

Copy link
Contributor

Choose a reason for hiding this comment

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

Curious because I do not know this part of the code yet; Is tracking realloc possible to do here?

Copy link
Contributor

Choose a reason for hiding this comment

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

We could probably add some kind of limit to the number of bytes realloc'd, not sure where that would come from

@brooksprumo
Copy link
Contributor

brooksprumo commented Nov 19, 2021

Would this allow the creation of unlimited accounts without data? Since there is storage overhead per account, I think that should be factored into this model as well, as the size/size limit should reflect the requirements imposed on the validators instead of the account creator/holder.

Or does space in the SystemInstruction already account for that?

@jeffwashington
Copy link
Contributor Author

Would this allow the creation of unlimited accounts without data? Since there is storage overhead per account, I think that should be factored into this model as well, as the size/size limit should reflect the requirements imposed on the validators instead of the account creator/holder.

Or does space in the SystemInstruction already account for that?

@taozhu-chicago tells me that there is already a # acct limit per slot.

Comment on lines +60 to +63
/// max len of account data in a slot (bytes)
pub const MAX_ACCOUNT_DATA_LEN: u64 = 100_000_000;
Copy link
Contributor

Choose a reason for hiding this comment

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

If max account data len is 100 MB, then getting to 1 TB would only take a bit over 1 hour, is that right? I don't know what the right number should be here though... If a DoS was happening, would 1 hour be long enough to prevent/handle it?

Math used: 10^12 B / 10^8 B * 0.4 s per slot / 60 s per min / 60 min per hour (double check in case I made dumb mistakes)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@carllin ran these calculations in his head about 15 times last night.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this comment was my invitation for him to comment ;-)

Copy link
Contributor

@carllin carllin Nov 20, 2021

Choose a reason for hiding this comment

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

Yeah about an hour. I think if we want to survive longer, we'll have to require validators have a lot more disk. Maybe 10TB minimum. How much space does it take to store 1 billion 10 MB accounts 😄 , 10 petabytes?

Also each account costs about $40, so if we factor that in, we could set the disk requirements based on the maximum economic attack we want to handle. 1 billion account would be 40 bil

Copy link
Member

Choose a reason for hiding this comment

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

We would want more like a week. How does this max compare with the current mainnet-beta account data average written per slot?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added the data len to the metrics. I'm waiting on the ability to start a validator with this to get the metrics of mnb. Keep in mind the current metrics ONLY measure the data size of account creation; not updates.

@tao-stones
Copy link
Contributor

number of writable accounts are counted into overall transaction cost, described in #20531.
There is a cost_weight multiplier to transaction cost, enables us to exponentially increase its value, should help in event a lot of account, regardless its size, to be created in a transaction.

@jeffwashington jeffwashington force-pushed the metrics98 branch 8 times, most recently from f17f7b3 to 7c03e67 Compare November 19, 2021 23:49
@brooksprumo
Copy link
Contributor

number of writable accounts are counted into overall transaction cost, described in #20531. There is a cost_weight multiplier to transaction cost, enables us to exponentially increase its value, should help in event a lot of account, regardless its size, to be created in a transaction.

@taozhu-chicago Is the MAX_WRITABLE_ACCOUNTS effectively the maximum number of new accounts per transaction?

const MAX_WRITABLE_ACCOUNTS: usize = 256;

And is MAX_WRITABLE_ACCOUNT_UNITS what controls the maximum number of new accounts per block?

pub const MAX_WRITABLE_ACCOUNT_UNITS: u64 = MAX_BLOCK_REPLAY_TIME_US * COMPUTE_UNIT_TO_US_RATIO;

@brooksprumo
Copy link
Contributor

There is a cost_weight multiplier to transaction cost, enables us to exponentially increase its value, should help in event a lot of account, regardless its size, to be created in a transaction.

@taozhu-chicago It looks like CostTracker::try_add() does factor in the cost weight (see line 87):

pub fn try_add(
&mut self,
_transaction: &SanitizedTransaction,
tx_cost: &TransactionCost,
) -> Result<u64, CostTrackerError> {
let cost = tx_cost.sum() * tx_cost.cost_weight as u64;
self.would_fit(&tx_cost.writable_accounts, cost, tx_cost.account_data_size)?;
self.add_transaction(&tx_cost.writable_accounts, cost, tx_cost.account_data_size);
Ok(self.block_cost)
}

But it doesn't look like cost weight is used by either add_transaction_cost() or would_transaction_fit():

pub fn would_transaction_fit(
&self,
_transaction: &SanitizedTransaction,
tx_cost: &TransactionCost,
) -> Result<(), CostTrackerError> {
self.would_fit(
&tx_cost.writable_accounts,
tx_cost.sum(),
tx_cost.account_data_size,
)
}
pub fn add_transaction_cost(
&mut self,
_transaction: &SanitizedTransaction,
tx_cost: &TransactionCost,
) {
self.add_transaction(
&tx_cost.writable_accounts,
tx_cost.sum(),
tx_cost.account_data_size,
);
}

Doing a quick grep tells me that neither add_transaction_cost() nor would_transaction_fit() are used anywhere, so maybe that's why this is OK. But this seems like a bug to me. Can you help me understand what I'm missing?

P.S. I know this is unrelated to this PR specifically.

@tao-stones
Copy link
Contributor

@taozhu-chicago Is the MAX_WRITABLE_ACCOUNTS effectively the maximum number of new accounts per transaction?

const MAX_WRITABLE_ACCOUNTS: usize = 256;

It is currently used as capacity used to create transactionCost.writable_accounts vec. It does not limits number of accounts.

@brooksprumo
Copy link
Contributor

brooksprumo commented Nov 23, 2021

It is currently used as capacity used to create transactionCost.writable_accounts vec. It does not limits number of accounts.

@taozhu-chicago Gotcha. What I'm ultimately interested in is making sure that new accounts without data are also included in this tracking, since each account has storage overhead. (See #21369 (comment) for original comment.)

Can you point me to where the number of new accounts is limited?

@tao-stones
Copy link
Contributor

And is MAX_WRITABLE_ACCOUNT_UNITS what controls the maximum number of new accounts per block?

pub const MAX_WRITABLE_ACCOUNT_UNITS: u64 = MAX_BLOCK_REPLAY_TIME_US * COMPUTE_UNIT_TO_US_RATIO;

For better parallelism, we discourage to packing too many transactions that write to same account. This limit is for this purpose. cost_tracker keeps tracking transaction cost per writable account in a block, transactions that would exceed any writable account limit are retried.

@tao-stones
Copy link
Contributor

@taozhu-chicago It looks like CostTracker::try_add() does factor in the cost weight (see line 87):

pub fn try_add(
&mut self,
_transaction: &SanitizedTransaction,
tx_cost: &TransactionCost,
) -> Result<u64, CostTrackerError> {
let cost = tx_cost.sum() * tx_cost.cost_weight as u64;
self.would_fit(&tx_cost.writable_accounts, cost, tx_cost.account_data_size)?;
self.add_transaction(&tx_cost.writable_accounts, cost, tx_cost.account_data_size);
Ok(self.block_cost)
}

But it doesn't look like cost weight is used by either add_transaction_cost() or would_transaction_fit():

pub fn would_transaction_fit(
&self,
_transaction: &SanitizedTransaction,
tx_cost: &TransactionCost,
) -> Result<(), CostTrackerError> {
self.would_fit(
&tx_cost.writable_accounts,
tx_cost.sum(),
tx_cost.account_data_size,
)
}
pub fn add_transaction_cost(
&mut self,
_transaction: &SanitizedTransaction,
tx_cost: &TransactionCost,
) {
self.add_transaction(
&tx_cost.writable_accounts,
tx_cost.sum(),
tx_cost.account_data_size,
);
}

Doing a quick grep tells me that neither add_transaction_cost() nor would_transaction_fit() are used anywhere, so maybe that's why this is OK. But this seems like a bug to me. Can you help me understand what I'm missing?

P.S. I know this is unrelated to this PR specifically.

Oh yeah. The weight is newly added feature, it's main use is still in a pending PR #21220. But for now, it is used to set vote's weight to zero, to allow all vote transactions bypass cost_model.
try_add has become the main entry point of cost_tracker, it hosts additional logic on top of book keeping (such as applying weight and maybe logic in the future). would_fit and add_transaction are just the actual book keeping functions, they deal with whatever cost given. Perhaps they can be made private if no one is using them (They used by ledger-tool at some point)

@tao-stones
Copy link
Contributor

Can you point me to where the number of new accounts is limited?

Mmm, it does not limits number of new accounts per se. Transaction cost is determined by instructions it has, and the number of writable accounts. These contributes to cost tracking.

@jeffwashington jeffwashington force-pushed the metrics98 branch 2 times, most recently from 93ce7fd to 6aa0601 Compare November 29, 2021 19:54
@brooksprumo
Copy link
Contributor

Overall this looks good to me. It is an incremental improvement, and further improvements can land in future PRs. I'd say mark this one as Ready For Review!

@jeffwashington jeffwashington marked this pull request as ready for review November 30, 2021 21:10
@jeffwashington jeffwashington force-pushed the metrics98 branch 2 times, most recently from 9fc2947 to 34ef9e2 Compare December 1, 2021 18:28
@codecov
Copy link

codecov bot commented Dec 6, 2021

Codecov Report

Merging #21369 (e8e25b8) into master (0224a8b) will decrease coverage by 0.1%.
The diff coverage is 88.6%.

@@            Coverage Diff            @@
##           master   #21369     +/-   ##
=========================================
- Coverage    81.6%    81.4%   -0.2%     
=========================================
  Files         511      511             
  Lines      143320   143509    +189     
=========================================
- Hits       116976   116878     -98     
- Misses      26344    26631    +287     

@jeffwashington
Copy link
Contributor Author

bench-tps #s I get locally are far lower than I expect, on 2 different colo machines, with both master and with this pr. The numbers seem similar, however. Will monitor overnight perf tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants