-
Notifications
You must be signed in to change notification settings - Fork 63
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
Pallet services-payment #77
Conversation
Thanks for starting the PR @notlesh. I think the way I imagine this happening is by having something like this:
What do you think¿ |
Shouldn't this be variable? Or is there another layer of fees for
Seems reasonable, but I have a couple thoughts:
This is in line with my design ideas so far, except that I was trying to prevent valuating block production until the latest point possible (again, I think block production cost should be variable and I expect volatility). Thanks for the initial feedback. Another idea I had, that I may not have articulated well, is the idea that containerChain block production could somehow implicitly pay for itself. This could be something like a special XCM instruction that's reserved for this purpose and executed immediately on block creation, or it could be something like a proof that the |
Ok, I've incorporated some of your suggestions:
|
I like this idea, and given that we expect some container chain to only produce blocks occasionally (think of price feeds that only update if the price changes significantly), there should be some way for container chains to pay a priority fee to ensure that they can produce a block as soon as possible. Haven't thought about this in much detail, but it seems to me that the problem of whether to produce a block for container chain A or container chain B is very similar to the problem of whether to include a transaction in a block. So we could benefit from implementing some state-of-the-art features such as a base fee, dynamic fees, etc. |
Good point, I haven't given much thought to this yet. A credit system like the one currently implemented is not a good choice when we want to support some kind of priority tip, and I think we should.
One idea I had was to implement the block production as a transaction/extrinsic. This would let us leverage existing fee mechanisms and txpool logic. I think its downside is that it wouldn't work well for chains that need more regular block production, but I'm not sure how much we want to cater to this or not. I think one thing we should keep in mind in our design is that a given |
The ability to produce blocks as fast as possible will come from the relay side of things (which for now we have not discussed how to support). This will have to do more with DOT than our own token. Essentially, there will be an auction for this. And its also more for parathreads (future work). Note that distinct collators will be assigned for container chain A or B in the case of parachains. The case of parathreads has to be better thought, so I would not spend that much time figuring that out (specially until we know how the relay will handle auctions for slots, etc) And for parachains, we only need them to have block credit to produce blocks for now. I would keep this incremental given the whole complexity and unknowns of the issue (let's make sure we support the parachain usecase, then we will migrate to whatever comes) |
I like this capped limit for now. BTW yes costs are expected to be variable (with usage likely), but we can do this in a separate PR. |
I thought about this and for the parachain usecase it really does defeat the purpose. It is probably a good option for parathreads though. But as said, we can evaluate later based on our needs. One thing we need to make sure is that Tanssi cannot receive the whole block or anything substantial as an input to an extrinsic, as this would add limit the capacity by the Tanssi capacity. The whole idea of the system is that container-chains are only limited by the weight and pov associated to their relay chain slot, and not any other parachain-slot. |
pallets/services-payment/src/lib.rs
Outdated
/// Provider of a block cost which can adjust from block to block | ||
type ProvideBlockProductionCost: ProvideBlockProductionCost<Self>; | ||
/// The maximum number of credits that can be accumulated | ||
type MaxCreditsStored: Get<u64>; |
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.
type MaxCreditsStored: Get<u64>; | |
type MaxCreditsStored: Get<Self::BlockNumber>; |
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 did this conceptually simple change in 2dc5fdb, but it was messy. It makes sense to constrain credits stored to the same type as BlockNumber
, but this bled into many other types. It would bleed into UX, too, which makes me hesitate.
I'll let you decide if you like it (or make suggestions).
I like the shape the PR is getting. What do you think about implementing inside the pallet in some module a couple of potential options for |
pallets/services-payment/src/lib.rs
Outdated
/// Handler for fee charging. This will be invoked when fees need to be deducted from the fee | ||
/// account for a given paraId. | ||
pub trait OnChargeForBlockCredit<T: Config> { | ||
fn withdraw_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.
fn withdraw_fee( | |
fn charge_block_production_credit( |
I feel withdraw_fee
implies already burning tokens, while I think the pallet is well designed to be more modular at this point. So I think we should use a more generic term here (although I am not the best with naming)
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 went with charge_credit
in 3da6f36 to be terse, lmk what you think
Co-authored-by: girazoki <[email protected]>
Thanks again for the feedback. I'm comfortable with the design given the early nature of the project, but I still have concerns. I think we could come back to those later, though. One I haven't voiced yet is the relationship with collator assignment and floating fees. In a txn world, there's always an opportunity cost associated with including a txn. In Ethereum, they call(ed) that an uncle fee -- it's the risk that including a txn is just enough extra work that someone else beats you to block authoring, leaving your block as an uncle. In Moonbeam, it's similar but not as pronounced since authoring slots are well-known. What I'm getting at is: when a collator gets assigned to a container parachain, it has no control over the opportunity cost for that assignment. In addition, it's expected to make blocks consistently, so paying a tip makes no sense. It could be that its assignment is really shitty and it does a lot of work for little reward, or maybe that this parachain ends up not keeping up with its block credits and so it never gets to author for them and so it receives no rewards. ...and I bring this up because: can we design this with some economic incentives that improve such a situation? I honestly like the parathread model better than the parachain model so for for this reason -- it's better for the orchestrator chain in general. Specifically, it has more room for the block author to decide which work is worth doing and which isn't. We can call this "off topic" for now, so thanks for thinking through it with me.
Yes, this could be good, and great for testing / early dev 👍
Yes, tests will come, I just wanted us to settle a bit on the design before doing so. I'm content with this PR as a first round implementation, so I can get started on tests if no one wants to make any big changes. |
I am happy to re-consider the design of the pallet if we find a common solution that might work with how we think parathreads will work in the future and parachains. But very likely we need to start serving parachains. One thing we can do is require that in order for collators to get assigned to parachains, parachains need to have credits at least for two sessions of block production, assuming a block production of, e.g., 12 seconds. This way a collator knows the parachain will have credit for serving blocks. And I am expecting parachain container-chains to have a similar block production rate than tanssi. One thing I did not mention is that the initial idea is that there is no distinction between rewards for Tanssi collators or container-chain collators, i.e., producing a block in a container-chain will count towards the rewards in Tanssi as much as if the block was produced in Tanssi. For the case of parachains, which have a constant block production, collators will likely be unable to choose whether they collate in one parachain or the other, they will simply get assigned to one parachain every session (and likely, they will rotate). The parathread model can be a bit different, in which we have a pool of collators that can choose to produce blocks based, among other things, on the priority, fee, tip or other variables. At the end it might well be that we have different service-payment constraints for both models, and even different collator-models. But this is hard to anticipate. I am open to suggestions if we find a common model satisfying both though |
{ | ||
#[pallet::call_index(0)] | ||
#[pallet::weight(0)] // TODO | ||
pub fn purchase_credits( |
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 might be desirable to be able to provide a maximum fee the caller is willing to pay per credit 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.
Yeah I think it might be useful. Can you add that?
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.
Added in 3a91bcf as an Option
al limit. LMK what you think.
pallets/services-payment/src/lib.rs
Outdated
|
||
let existing_credits = BlockProductionCredits::<T>::get(para_id).unwrap_or(T::BlockNumber::zero()); | ||
let updated_credits = existing_credits.saturating_add(credits); | ||
ensure!( |
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 maybe buy as many credit as we can to fill the credit buffer here instead of erroring.
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.
Added in 188e2d2.
One detail to note: if there are 0 purchases possible (even if 0 were requested) the extrinsic succeeds and a CreditsPurchased
event will be emitted with credits_purchased == 0
.
This case is a bit ambiguous to me, but this seemed like the most graceful thing to do. We could bloat the event with one more field for clarity: credits_requested
.
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 think the pallet is good enough as it is for now if we fix the couple of comments that I have. @nanocryk @tmpolaczyk can you take a look at this?
pallets/services-payment/src/lib.rs
Outdated
#[pallet::genesis_build] | ||
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { | ||
fn build(&self) { | ||
todo!(); |
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.
Won't this panic on genesis? If we don't want to implement it just make it a noop.
todo!(); | |
// TODO: implement |
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.
Thanks, fixed in a4b0d36
Also run |
I believe this PR is ready to go (feedback addressed, CI is green). PTAL and feel free to merge, @girazoki. |
This PR implements a basic fee payment mechanism for Tanssi. Should solve #10.
This initial PR starts with a bare-bones implementation which relies on:
orchestratorChain
itselforchestratorChain
currency acceptedpara_id -> num_credits
.The fee charged for purchasing a credit is runtime-configurable via the
OnChargeForBlockCredit
trait. This could, for example, implement a dynamic fee mechanism.To prevent excessive hoarding of cheaply-purchased credits, a runtime-configurable maximum (
T::MaxCreditsStored
) is applied.Future work: Whenever a block is created for the given para,
burn_credit_for_para()
can be called to erase one of the credits. This errors if there are no credits available, and so should be called before work is done to create a block if possible.