-
Notifications
You must be signed in to change notification settings - Fork 17
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
Implement contract state rent #32
base: main
Are you sure you want to change the base?
Conversation
58c85e8
to
3d5783c
Compare
3d5783c
to
b71a4e0
Compare
string unit_rent_price = 3 | ||
[ | ||
(gogoproto.moretags) = "yaml:\"unit_gas_price\"", | ||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", |
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.
i imagine this would be such a small value, would it be more worth it to make this param per 10kb
per block or something and then have the unit price represented as an int (similar to how the wasm vm gas multiplier is represented)
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 can do price per 10kb or more but I'd prefer keeping the type as dec for flexibility just in case in the future we want to decrease price by a lot
} | ||
s.parent.Set(key, value) | ||
if key != nil { | ||
s.sizeChange += int64(len(key)) |
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.
do we care about key size? i thought the limiting factor on performance for keys was key cardinality due to tree traversal operational time.
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 care about key size for the storage space it consumes
// has to perform a read here to know the size change | ||
value := s.Get(key) | ||
s.parent.Delete(key) | ||
if value != nil { |
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.
is it possible for a key to be set with a nil value (such that the key existed previously). in this case, we would still need to decrement for keyLen bytes, right?
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.
it's not possible and validated here https://github.com/sei-protocol/sei-cosmos/blob/main/store/cachekv/store.go#L132
// CacheWrap implements the KVStore interface. It panics as a Store | ||
// cannot be cache wrapped. | ||
func (s *Store) CacheWrap(_ types.StoreKey) types.CacheWrap { | ||
panic("cannot CacheWrap a ListenKVStore") |
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 curious, what is the normal behavior of cachewrap, and why do we want it to panic here?
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.
It's used for store types that need the ability the "branch out" on that particular inheritance level. Unfortunately a lot of functions' parameter are using the interface that includes these methods. Panicing for wrapper store types that don't need branching at their inheritance levels seem to be common practice for types like gaskv/listenkv/tracekv
gas := k.runtimeGasForContract(ctx) | ||
res, gasUsed, err := k.wasmVM.Migrate(newCodeInfo.CodeHash, env, msg, &prefixStore, cosmwasmAPI, &querier, k.gasMeter(ctx), gas, costJSONDeserialization) | ||
res, gasUsed, err := k.wasmVM.Migrate(newCodeInfo.CodeHash, env, msg, sizedPrefixStore, cosmwasmAPI, &querier, k.gasMeter(ctx), gas, costJSONDeserialization) |
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.
is it ok that we're no longer passing in the pointer (see &prefixStore
) (instead passing sized store)?
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.
sizedPrefixStore
is a pointer
rentInfo.Balance -= rentToBeCharged | ||
} | ||
rentInfo.LastChargedBlock = uint64(ctx.BlockHeight()) | ||
k.setRentInfo(ctx, contractAddress, rentInfo) |
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.
should we put this after the error check for slight optimization (then the write doesnt have to occur to just rollback if the balance is negative)
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.
hm good point i'm doing it wrong here now that you mentioned rollback. My intention was to persist the negative balance so that the contract owner would still be responsible for the accrued rent for his state since the last time rent was charged even if the most recent charge attempt results in out of rent, but forgot it wouldn't persist due to the rollback. We might need to do something like:
DeliverTx -> branch cachekv
WASM execution -> branch of branch of cachekv
Charge rent
-> if success, commits branch of branch
-> if out of rent, revert branch of branch
DeliverTX -> branch cachekv commits/aborts regardless of out of rent or not
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.
actually a better way would be to place the rent deduction logic into ante handler and the check in msgServer, so that we leverage the existing rollback infra instead of throwing in yet another layer
} | ||
rentInfo.LastChargedBlock = uint64(ctx.BlockHeight()) | ||
k.setRentInfo(ctx, contractAddress, rentInfo) | ||
if rentInfo.Balance < 0 { |
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 to confirm, the expected behavior here is that if a contract has insufficient duration rent, then the contract is borked until the deposit rent tx is done, right? and this would result in any executes, migrates, etc to fail
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 that's correct
@@ -1081,6 +1215,18 @@ func (c BankCoinTransferrer) TransferCoins(parentCtx sdk.Context, fromAddr sdk.A | |||
return nil | |||
} | |||
|
|||
func (c BankCoinTransferrer) TransferCoinsToModule(ctx sdk.Context, fromAddr sdk.AccAddress, recipientModule string, amount sdk.Coins) error { |
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.
nit, we may want to use the deferred send to module for this if we expect a lot of these action to occur (assuming this only happens on deposit rent, so maybe its infrequent enough as to not degrade concurrency, but if it becomes problematic we can make this a concurrent safe process)
assert.Equal(t, uint64(ctx.BlockHeight()), rentInfo.LastChargedBlock) | ||
|
||
// bump block height | ||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) |
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.
can we add a test case for two txs in the same block that modify state size each?
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 good call
"errors" | ||
) | ||
|
||
// custom struct instead of protobuf to minimize state needed |
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.
why not just use protobuf for consistency with other store objects?
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.
i was thinking that protobuf might be too heavy for such a simple struct but yeah i guess it probably won't be that bad. Better keep it consistent. Will change
Summary
Implement duration-based fee (rent) based on https://docs.google.com/document/d/1FbbV3vQZnyM1xgZF-ug7zNCVlt_3Z9aD7Hng2OIveX0/edit
Specifically, each contract will have a counter tracking the number of bytes it owns as well as the amount of
usei
people have deposited for it as rent. Each time a write operation happens to a contract (i.e.execute
/sudo
/migrate
/reply
), it will settle the rent charges since the last write. For example, if a contract was last written at block height 15 with a post-write state size of 500 bytes, and it's now written again at block height 20, it would need to pay (20 - 15) * 500 *rent price
out of its rent balance. If the balance is insufficient, the write would fail.rent price
is a wasm module param that can be updated via gov proposal. Anyone can deposit rent for any contract via theDepositRent
transaction type. Note that deposited rent cannot be withdrawn.One thing to note is that in order to track the size of a contract, we need to do an additional query before each
Set
andDelete
to update the size properly.Future steps:
Testing