Comportement actuel des frais

Je ne comprends pas le comportement actuel des frais de transaction.

En théorie

Frais constants égaux à 1 :

impl WeightToFee
{
    fn weight_to_fee(_weight: &Weight) -> Self::Balance {
        // Force constant fees for now
        One::one()
    }
}

En pratique (tests end2end)

Frais constants égaux à 2 (cĞD) :

Feature: Balance transfer all
  Scenario: If bob sends all his ĞDs to Dave
    When bob sends all his ĞDs to dave
    """
    Bob is a member, as such he is not allowed to empty his account completely,
    if he tries to do so, the existence deposit (2 ĞD) must remain.
    """
    Then bob should have 2 ĞD
    """
    10 ĞD (initial Bob balance) - 2 ĞD (Existential deposit) - 0.02 ĞD (transaction fees)
    """
    Then dave should have 798 cĞD

En pratique (duniter --dev)

Quand je fais un balance.transfer() de Bob vers Charlie dans polkadotjs, il y a un frais de 1 prélevé, et envoyé à Alice (validateur).

(dans ma branche actuelle ça va à la trésorerie, mais c’est le même montant).

D’où peut bien venir cette différence de comportement ??

pallets_config.rs

impl pallet_transaction_payment::Config for Runtime {
    /* ... */
    #[cfg(not(feature = "runtime-benchmarks"))]
    type WeightToFee = common_runtime::fees::WeightToFeeImpl<Balance>;
    #[cfg(feature = "runtime-benchmarks")]
    type WeightToFee = frame_support::weights::ConstantMultiplier::<u64, sp_core::ConstU64<0u64>>;
    /* ... */
}

ce qui explique des comportements différents mais pas les frais de 2, qui devraient être à 0.

C’est normal de désactiver les frais pour les benchmarks (mais ça peut fausser nos mesures pour les quotas), par contre les tests end2end et mon test local utilisent le même binaire compilé avec les options par défaut, donc ça n’explique pas la différence.

Ah oui j’ai confondu… Le mystère reste entier.

De la variable DUNITER_GENESIS_CONFIG, qui ne paramètre pas le même Genesis dans le cas d’utilisation de --dev selon que la variable est définie ou non. Cf : État des lieux des différentes chaînes

C’est bien piégeux !

edit : ah ben non, c’est pas si évident, j’ai répondu intuitivement mais je ne vois pas où sont précisés les frais.

edit 2 : ou alors l’exécutable des tests end2end n’est pas le même que celui que tu utilises avec Polkadotjs :confused:

C’est dans le fichier runtime/common/src/fees.rs qui convertit les weight en fees. Et ensuite, c’est ce que disait tuxmain, c’est configuré dans runtime/common/src/pallets_config.rs. Mais donc il n’y a pas moyen d’avoir deux runtime avec des règles différentes.

Le binaire utilisé par les tests end2end est le même que celui que j’ai utilisé pour mes tests manuels.

// dans end2end-tests/tests/common/mod.rs
const DUNITER_LOCAL_PATH: &str = "../target/debug/duniter";

J’ai ajouté un test d’intégration sur les frais, lui aussi dit que les frais sont de 2.

/// test currency transfer with extrinsic
// the signer account should pay fees and a tip
// the treasury should get the fees
#[test]
fn test_transfer_xt() {
    ExtBuilder::new(1, 3, 4)
        .with_initial_balances(vec![
            (AccountKeyring::Alice.to_account_id(), 10_000),
            (AccountKeyring::Eve.to_account_id(), 10_000),
        ])
        .build()
        .execute_with(|| {
            let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death {
                dest: AccountKeyring::Eve.to_account_id().into(),
                value: 500,
            });

            // 1 cĞD of tip
            let xt = get_unchecked_extrinsic(call, 4u64, 8u64, AccountKeyring::Alice, 1u64);
            let info = xt.get_dispatch_info();
            println!("dispatch info:\n\t {:?}\n", info);

            // nothing in treasury at start
            // FIXME treasury is initialized at ED
            assert_eq!(Balances::free_balance(Treasury::account_id()), 200);
            // Alice gives 500 to Eve
            assert_ok!(Executive::apply_extrinsic(xt));
            // check amounts
            assert_eq!(
                Balances::free_balance(AccountKeyring::Alice.to_account_id()),
                10_000 - 500 - 3 // initial - transfered - fees
            );
            assert_eq!(
                Balances::free_balance(AccountKeyring::Eve.to_account_id()),
                10_000 + 500 // initial + transfered
            );
            assert_eq!(Balances::free_balance(Treasury::account_id()), 203);
        })
}

J’ai mis du temps à y arriver parce que j’avais initialisé la trésorerie à zéro et que donc il ne pouvait pas recevoir les frais parce qu’il était en dessous du dépôt existentiel (cf Soldes des comptes au genesis v2 - #7 by HugoTrentesaux). Donc j’ai lu de long en large le code de substrate sur l’exécution d’un extrinsic (pallet-executive et primitives associées) et le paiement des frais de transaction (pallet-transaction-payment et toute la mécanique du imbalance…). Bref c’était long ><

Par contre, le test manuel donne toujours la même chose :

Et la trésorerie était dans tous les cas initialisée à 2 ĞD, il y a un conflit entre balances et duniter-account je crois.

image

La question du montant initial de la trésorerie sera réglée dans la MR !182 de cgeek, mais la question du comportement des frais reste entière. Dans tous les tests d’intégration et end2end, le montant des frais est de 2 cĞD, mais les tests manuels donnent toujours 1 cĞD.

En posant un point d’arrêt dans la pallet pallet-transaction-payment dans une des méthodes qui comporte le mot fee, j’en arrive à la méthode final_fee qui est celle qui finit par donner un total de 2 ĞD en frais si lancé comme suit (mode e2e) :

cargo run -- --chain=gdev_dev --execution=Native --sealing=manual --force-authoring --rpc-cors=all --tmp --ws-port 9944 --alice

Tandis que si lancé comme suit (mode manuel) :

cargo run -- --dev

Alors je n’ai effectivement que 1 ĞD en final_fee.

A l’inspection je vois que c’est adjusted_weight_fee qui vaut 1 ĞD en mode e2e, alors qu’il vaut 0 en mode manuel :

Et en creusant, tu verras en posant un point d’arrêt dans pallet-transaction-payment.compute_fee_raw() que c’est la méthode weight_to_fee qui renvoie 1 plutôt que 0 et vient incrémenter la valeur des frais.

Sur ma branche cela s’explique par le fait que les poids calculés par Benjamin sont pris en compte dans un cas et pas dans l’autre. Sur master, je suppose que c’est pareil.

Je te laisse vérifier tout cela, ce sera l’occasion de débogguer Substrate :slight_smile:

4 Likes

Normalement pour l’instant les poids ne sont pas du tout pris en compte puisque la fonction weight_to_fee est une constante,

Et l’implémentation (dans runtime/common/src/fees.rs, en version abrégée dans mon premier post) :

pub struct WeightToFeeImpl<T>(sp_std::marker::PhantomData<T>);

impl<T> WeightToFee for WeightToFeeImpl<T>
where
    T: BaseArithmetic + From<u32> + Copy + Unsigned,
{
    type Balance = T;

    fn weight_to_fee(_weight: &Weight) -> Self::Balance {
        // Force constant fees for now
        One::one()
    }
}

Par contre effectivement vue que

On peut aller chercher la raison du côté du FeeMultiplier et tomber sur cet élément du storage :

#[pallet::storage]
#[pallet::getter(fn next_fee_multiplier)]
pub type NextFeeMultiplier<T: Config> =
	StorageValue<_, Multiplier, ValueQuery, NextFeeMultiplierOnEmpty>;

Cette valeur vaut 1,000,000,000,000,000,000 en “mode e2e” et 0 en “mode manuel”.

L’argument --sealing=manual semble être responsable de ce changement (ou alors c’est juste la valeur au bloc zéro que j’observe uniquement en sealing manual ?). Merci de m’avoir mis sur le bon chemin, je vais peut-être laisser Benjamin creuser ça après avoir finalisé les benchmarks !

2 Likes

Effectivement dès que je fais un appel RPC engine.createBlock(Yes,No) la variable adjusted_weight_fee repasse à 0.

Voici le même test e2e qu’avant, passant, mais avec 0,01 ĞD de frais :

@genesis.default
Feature: Balance transfer all

  Scenario: If bob sends all his ĞDs to Dave
    # Attendre un bloc
    When 1 block later
    When bob sends all her ĞDs to dave
    """
    Bob is a member, as such he is not allowed to empty his account completely,
    if he tries to do so, the existence deposit (1 ĞD) must remain.
    """
    Then bob should have 1 ĞD
    """
    10 ĞD (initial Bob balance) - 1 ĞD (Existential deposit) - 0.01 ĞD (transaction fees)
    """
    Then dave should have 899 cĞD
Feature: Balance transfer all
  Scenario: If bob sends all his ĞDs to Dave
   ✔  When 1 block later
   ✔  When bob sends all her ĞDs to dave
   ✔  Then bob should have 1 ĞD
   ✔  Then dave should have 899 cĞD
1 Like

C’est @bgallois qui a proposé le fix ici, et on va donc utiliser un ConstFeeMultiplier de One dans un premier temps et si on a envie de modifier par la suite on pourra :slight_smile:

pub FeeMultiplier: pallet_transaction_payment::Multiplier = pallet_transaction_payment::Multiplier::one();
type FeeMultiplierUpdate = pallet_transaction_payment::ConstFeeMultiplier<FeeMultiplier>;

Sur ma branche, les frais sont toujours de 2 (1 pour le calcul et 1 pour le stockage), on pourra ajuster ça une fois les benchmarks calculés, et il faudra faire des calculs d’ordre de grandeur et des choix sur le coût du calcul vs le coût du stockage.

1 Like