Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c7bd1c5
Prioritize Substrate txns by tip-per-effective-gas
notlesh Nov 16, 2022
d63b5c4
s/<spaces>/<tabs>/
notlesh Nov 16, 2022
85fa317
fmt
notlesh Nov 16, 2022
90667d9
Remove unused import
notlesh Nov 16, 2022
c9a1dbd
Remove useless calls to setBlocksPerRound
notlesh Nov 16, 2022
3f08d1c
wtf
notlesh Nov 16, 2022
5988d66
Replace createBlock([]) with one block per extrinsic
notlesh Nov 17, 2022
a7eb034
Merge branch 'master' into notlesh-fix-txpool-fairness
notlesh Dec 14, 2022
5ada814
A more performant workaround for txn ordering
notlesh Dec 15, 2022
a4eabc2
Merge branch 'master' into notlesh-fix-txpool-fairness
notlesh Jan 3, 2023
4179c44
prettier
notlesh Jan 3, 2023
644ad65
Include some basic tests
notlesh Jan 6, 2023
9087f55
Fix test
notlesh Jan 12, 2023
40bb3ca
Merge branch 'master' into notlesh-fix-txpool-fairness
notlesh Jan 12, 2023
d904ee5
Add txn replacement tests
notlesh Jan 12, 2023
27f3e83
prettier
notlesh Jan 12, 2023
eeb4482
Merge branch 'master' into notlesh-fix-txpool-fairness
notlesh Jan 23, 2023
68657b4
Don't assume nonce is 0
notlesh Jan 23, 2023
f31be56
Update tests/tests/test-txpool/test-txpool-fairness.ts
notlesh Jan 30, 2023
9729146
Update tests/tests/test-txpool/test-txpool-fairness.ts
notlesh Jan 30, 2023
43bcc2d
Update tests/tests/test-txpool/test-txpool-fairness.ts
notlesh Jan 30, 2023
6a1914e
Update tests/tests/test-txpool/test-txpool-fairness.ts
notlesh Jan 30, 2023
f32ae96
Update tests/tests/test-staking/test-staking-locks.ts
notlesh Jan 30, 2023
89c6547
Update tests/tests/test-staking/test-staking-locks.ts
notlesh Jan 30, 2023
2fe5667
Add some (failing) tests around validate_transaction
notlesh Jan 30, 2023
2d5503f
Remove failing tests
notlesh Jan 31, 2023
9f6c527
Remove unused imports
notlesh Jan 31, 2023
ae1298f
Attempt to break up extremely long import line
notlesh Jan 31, 2023
9af007e
Another attempt
notlesh Jan 31, 2023
90b03d0
Respect my authoritah
notlesh Jan 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 9 additions & 20 deletions runtime/moonbase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ use sp_runtime::{
transaction_validity::{
InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError,
},
ApplyExtrinsicResult, FixedPointNumber, Perbill, Permill, Perquintill, SaturatedConversion,
ApplyExtrinsicResult, FixedPointNumber, Perbill, Permill, Perquintill,
};
use sp_std::{
convert::{From, Into},
Expand Down Expand Up @@ -1397,8 +1397,10 @@ moonbeam_runtime_common::impl_runtime_apis_plus_common! {
let dispatch_info = xt.get_dispatch_info();

// If this is a pallet ethereum transaction, then its priority is already set
// according to gas price from pallet ethereum. If it is any other kind of transaction,
// we modify its priority.
// according to effective priority fee from pallet ethereum. If it is any other kind of
// transaction, we modify its priority. The goal is to arrive at a similar metric used
// by pallet ethereum, which means we derive a fee-per-gas from the txn's tip and
// weight.
Ok(match &xt.0.function {
RuntimeCall::Ethereum(transact { .. }) => intermediate_valid,
_ if dispatch_info.class != DispatchClass::Normal => intermediate_valid,
Expand All @@ -1412,31 +1414,18 @@ moonbeam_runtime_common::impl_runtime_apis_plus_common! {
}
};

// Calculate the fee that will be taken by pallet transaction payment
let fee: u64 = TransactionPayment::compute_fee(
xt.encode().len() as u32,
&dispatch_info,
tip,
).saturated_into();

// Calculate how much gas this effectively uses according to the existing mapping
let effective_gas =
Comment thread
notlesh marked this conversation as resolved.
<Runtime as pallet_evm::Config>::GasWeightMapping::weight_to_gas(
dispatch_info.weight
);

// Here we calculate an ethereum-style effective gas price using the
// current fee of the transaction. Because the weight -> gas conversion is
// lossy, we have to handle the case where a very low weight maps to zero gas.
let effective_gas_price = if effective_gas > 0 {
fee / effective_gas
let tip_per_gas = if effective_gas > 0 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should also add an associated rust test for this validation function (if possible)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added some tests, but they're not in good shape. The call to Executive::validate_transaction early in the fn fails on an unsigned UncheckedExtrinsic except for an Ethereum one, and signing one is proving difficult.

I'll revisit this again with a fresh mind, but we may also want to consider creating a task to do these tests later.

In any case, I agree that they're a good idea...

tip.saturating_div(effective_gas as u128)
} else {
// If the effective gas was zero, we just act like it was 1.
fee
0
};

// Overwrite the original prioritization with this ethereum one
intermediate_valid.priority = effective_gas_price;
intermediate_valid.priority = tip_per_gas as u64;
intermediate_valid
}
})
Expand Down
30 changes: 30 additions & 0 deletions runtime/moonbase/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3103,6 +3103,36 @@ fn evm_success_keeps_substrate_events() {
});
}

#[test]
fn validate_transaction_fails_on_filtered_call() {
use sp_runtime::transaction_validity::{
InvalidTransaction, TransactionSource, TransactionValidityError,
};
use sp_transaction_pool::runtime_api::runtime_decl_for_TaggedTransactionQueue::TaggedTransactionQueueV3; // editorconfig-checker-disable-line

ExtBuilder::default().build().execute_with(|| {
let xt = UncheckedExtrinsic::new_unsigned(
pallet_evm::Call::<Runtime>::call {
source: Default::default(),
target: H160::default(),
input: Vec::new(),
value: Default::default(),
gas_limit: Default::default(),
max_fee_per_gas: Default::default(),
max_priority_fee_per_gas: Default::default(),
nonce: Default::default(),
access_list: Default::default(),
}
.into(),
);

assert_eq!(
Runtime::validate_transaction(TransactionSource::External, xt, Default::default(),),
Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
);
});
}

#[cfg(test)]
mod fee_tests {
use super::*;
Expand Down
95 changes: 61 additions & 34 deletions tests/tests/test-staking/test-staking-locks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import {
execTechnicalCommitteeProposal,
notePreimage,
} from "../../util/governance";
import { GLMR, MIN_GLMR_STAKING, MIN_GLMR_DELEGATOR } from "../../util/constants";
import {
MICROGLMR,
MILLIGLMR,
GLMR,
MIN_GLMR_STAKING,
MIN_GLMR_DELEGATOR,
} from "../../util/constants";
import { describeDevMoonbeam, DevTestContext } from "../../util/setup-dev-tests";
import { KeyringPair } from "@polkadot/keyring/types";
import { expectOk } from "../../util/expect";
Expand Down Expand Up @@ -200,9 +206,6 @@ describeDevMoonbeam("Staking - Locks - execute revoke", (context) => {
before("setup account balance", async function () {
await expectOk(
context.createBlock([
context.polkadotApi.tx.sudo.sudo(
context.polkadotApi.tx.parachainStaking.setBlocksPerRound(5)
),
context.polkadotApi.tx.balances.transfer(
randomAccount.address,
MIN_GLMR_DELEGATOR + 1n * GLMR
Expand Down Expand Up @@ -258,9 +261,6 @@ describeDevMoonbeam("Staking - Locks - multiple delegations single revoke", (con

await expectOk(
context.createBlock([
context.polkadotApi.tx.sudo.sudo(
context.polkadotApi.tx.parachainStaking.setBlocksPerRound(5)
),
context.polkadotApi.tx.balances.transfer(randomAccount.address, 2n * MIN_GLMR_STAKING),
context.polkadotApi.tx.parachainStaking
.joinCandidates(MIN_GLMR_STAKING, 1)
Expand Down Expand Up @@ -516,6 +516,11 @@ describeDevMoonbeam("Staking - Locks - bottom and top delegations", (context) =>

before("setup candidate & delegations", async function () {
this.timeout(20000);
const numBottomDelegations =
await context.polkadotApi.consts.parachainStaking.maxBottomDelegationsPerCandidate.toNumber();

const numTopDelegations =
await context.polkadotApi.consts.parachainStaking.maxTopDelegationsPerCandidate.toNumber();

// Create the delegators to fill the lists
bottomDelegators = new Array(
Expand All @@ -533,7 +538,7 @@ describeDevMoonbeam("Staking - Locks - bottom and top delegations", (context) =>
context.createBlock(
[...bottomDelegators, ...topDelegators].map((account, i) =>
context.polkadotApi.tx.balances
.transfer(account.address, MIN_GLMR_DELEGATOR + 2n * GLMR)
.transfer(account.address, MIN_GLMR_DELEGATOR + 20n * GLMR)
.signAsync(alith, { nonce: i })
)
)
Expand All @@ -543,39 +548,61 @@ describeDevMoonbeam("Staking - Locks - bottom and top delegations", (context) =>
it("should be set for bottom and top list delegators", async function () {
await expectOk(
context.createBlock(
[...topDelegators].map((account, i) =>
context.polkadotApi.tx.parachainStaking
.delegate(alith.address, MIN_GLMR_DELEGATOR + 1n * GLMR, topDelegators.length + 1, 1)
.signAsync(account)
)
[...topDelegators].map((account, i) => {
// add a tip such that the delegation ordering will be preserved, e.g. the first txns sent
// will have the highest tip
let tip = BigInt(topDelegators.length - i + 1) * MILLIGLMR;
return context.polkadotApi.tx.parachainStaking
.delegate(alith.address, MIN_GLMR_DELEGATOR + 1n * GLMR, i + 1, 1)
.signAsync(account, { tip });
})
Comment on lines +551 to +558
Copy link
Copy Markdown
Contributor

@nbaztec nbaztec Jan 30, 2023

Choose a reason for hiding this comment

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

This can be simplified byusing tip-- as be build the tx:

let tip = 1 * GLMR;
await expectOk(
      context.createBlock(
        [...topDelegators].map((account, i) => {
          // add a tip such that the delegation ordering will be preserved, e.g. the first txns sent
          // will have the highest tip
          return context.polkadotApi.tx.parachainStaking
            .delegate(alith.address, MIN_GLMR_DELEGATOR + 1n * GLMR, i + 1, 1)
            .signAsync(account, { tip: tip-- });
        })
      )
    );

)
);

// allow more block(s) for txns to be processed...
// note: this only seems necessary when a tip is added, otherwise all 300 txns make it into a
// single block. A tip is necessary if the txns are not otherwise executed in order of
// submission, which is highly dependent on txpool prioritization logic.
// TODO: it would be good to diagnose this further: why does adding a tip appear to reduce the
// number of txns included?
const numBlocksToWait = 1;
let numBlocksWaited = 0;
while (numBlocksWaited < numBlocksToWait) {
await context.createBlock();
const topLocks = await context.polkadotApi.query.balances.locks.multi(
topDelegators.map((delegator) => delegator.address)
);
let numDelegatorLocks = topLocks.filter((lockSet) =>
lockSet.find((lock) => lock.id.toHuman().toString() == "stkngdel")
).length;

if (numDelegatorLocks < topDelegators.length) {
numBlocksWaited += 1;
expect(numBlocksWaited).to.be.lt(
numBlocksToWait,
"Top delegation extrinsics not included in time"
);
} else {
expect(numDelegatorLocks).to.eq(topDelegators.length, "More delegations than expected");
break;
}
}

await expectOk(
context.createBlock(
[...bottomDelegators].map((account, i) =>
context.polkadotApi.tx.parachainStaking
.delegate(
alith.address,
MIN_GLMR_DELEGATOR,
topDelegators.length + bottomDelegators.length + 1,
1
)
.signAsync(account)
)
[...bottomDelegators].map((account, i) => {
// add a tip such that the delegation ordering will be preserved, e.g. the first txns sent
// will have the highest tip
let tip = BigInt(topDelegators.length + (bottomDelegators.length - i) + 1) * MILLIGLMR;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same as above

return context.polkadotApi.tx.parachainStaking
.delegate(alith.address, MIN_GLMR_DELEGATOR, topDelegators.length + i + 1, 1)
.signAsync(account, { tip });
})
)
);

const topLocks = await context.polkadotApi.query.balances.locks.multi(
topDelegators.map((delegator) => delegator.address)
);
expect(
topLocks.filter((lockSet) =>
lockSet.find((lock) => lock.id.toHuman().toString() == "stkngdel")
).length
).to.equal(
context.polkadotApi.consts.parachainStaking.maxTopDelegationsPerCandidate.toNumber()
);

// note that we don't need to wait for further blocks here because bottom delegations is much
// smaller than top delegations, so all txns reliably fit within one block.
const bottomLocks = await context.polkadotApi.query.balances.locks.multi(
bottomDelegators.map((delegator) => delegator.address)
);
Expand Down
Loading