paritytech / polkadot-sdk

The Parity Polkadot Blockchain SDK
https://polkadot.com/
1.9k stars 696 forks source link

Research and implement runtime API so node can validate bids (valid account, minimum price, ...) #724

Open eskimor opened 1 year ago

eskimor commented 1 year ago

Block producers need to be able to verify incoming bids in order to immediately reject invalid ones (and punish/disconnect peers providing them). We need some runtime API providing this functionality. Likely the call will accept a batch (vec/bounded vec) of bids and provides information on their validity (Likely just a bool, true for valid, false for invalid).

Something similar exists for transactions apparently: Draw inspiration from there, figure out what can be re-used.

A bid is valid for inclusion in the next block, if all of the following hold true:

Obviously the same checks will be done on block import/block creation.

*) If not the sender is just wasting money: Paying for a core that is not usable, but this would mean that random people can make any parathread produce a block, which might not be desirable. Also if we limit the number of blocks to be be bid on at a given time per parathread, this could result in DoS (costly, but still): We might be able to punt on that one for a first run.

rphmeier commented 1 year ago

My understanding is that bids would be regular Substrate extrinsics/transactions or possibly UnsignedTransactions with a special validate_unsigned method, and we could use the standard extrinsic validation APIs to achieve all goals. We should be able to use the code that Substrate already has for TX queuing/pruning/invalidity checking

eskimor commented 1 year ago

Fee calculation

Fee calculation pallet: https://paritytech.github.io/substrate/master/pallet_transaction_payment/index.html More info: https://docs.substrate.io/build/tx-weights-fees/

Other forms of fees (bonds, deposits, ...) exist as well. Deposits will be needed for registering a CollatorId. Dispatch class for orders will be "normal". Classification can be dynamic.

Unsigned Extrinsics

Unsigned extriniscs need more care than signed extrinisics. E.g. CheckNonce only exists for signed extrinsics.

Unsigned extrinsics are checked via ValidateUnsigned.

Example implementation for claims. For slashing.

Tipping would also need to be implemented manually.

Signed Extrinsics

Signed extensions on Kusama:

        let extra: SignedExtra = (
            frame_system::CheckNonZeroSender::<Runtime>::new(),
            frame_system::CheckSpecVersion::<Runtime>::new(),
            frame_system::CheckTxVersion::<Runtime>::new(),
            frame_system::CheckGenesis::<Runtime>::new(),
            frame_system::CheckMortality::<Runtime>::from(generic::Era::mortal(
                period,
                current_block,
            )),
            frame_system::CheckNonce::<Runtime>::from(nonce),
            frame_system::CheckWeight::<Runtime>::new(),
            pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
        );

signed extensions can be used to define checks that all transactions need to pass. We would only need validation for our new extrinsic. If it is unsigned, this should be doable via ValidateUnsigned above. For a signed extrinsic, this could work like this ... is there a better way? Actually: In the signed case we should not strictly require any additional validation: We can charge someone, so it is "valid" - even if not, we can accept and charge the account - so it is "valid" in the sense, that it is safe to put into a block.

Summary

For signed extrinsic which we can use in the proxy account scenario, we don't really need to worry about validation too much. The only things to figure out:

  1. How to customize fees - hints already above.
  2. How to implement such a secure proxy account.

With an unsigned extrinsic we can provide our own ValidateUnsigned implementation and use that to implement orders via CollatorId. Full flexibility, but also no checks by default hence easier to get wrong. Above list (SignedExtra) provides some things like nonce, weight checks, ... we should consider for aValidateUnsigned` variant.

rphmeier commented 1 year ago

Actually: In the signed case we should not strictly require any additional validation: We can charge someone, so it is "valid" - even if not, we can accept and charge the account - so it is "valid" in the sense, that it is safe to put into a block.

Indeed. if the transaction has no effect the user still pays for the weight used. It's like making a balance transfer which transfers more funds than exist in the account. It's actually crucial for blockchains to be able to include such transactions, because it can create block author DoS if they must be discarded (block author spends time/resources that are not reflected in the block itself)

However, there is another concern here. It might be that well-meaning but under-bidding transactions are included too early. i.e. any time a collator issues a bid, the collator should expect to be charged for the weight of processing the bid on-chain. This actually disincentivizes bids which are nonsensical, as every bid carries a fixed cost, and should reduce spam.

rphmeier commented 1 year ago

How to implement such a secure proxy account.

Proxies are quite simple to implement, actually. There is a ProxyKind enum defined in every runtime that uses the proxy pallet, and it implements CallFilter or some similar trait that allows allowed call types by the proxy to be filtered.