Skip to content

Commit 8ab9b7e

Browse files
cryptoAtwillkarlem
authored andcommitted
feat(node): default executor finish message with hook (#4)
* initial commit * expose default gas hook * review feedbacks
1 parent 342a357 commit 8ab9b7e

4 files changed

Lines changed: 218 additions & 50 deletions

File tree

fvm/src/executor/default.rs

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ use crate::kernel::{Block, ClassifyResult, Context as _, ExecutionError, Kernel}
2424
use crate::machine::{BURNT_FUNDS_ACTOR_ID, Machine, REWARD_ACTOR_ID};
2525
use crate::trace::ExecutionTrace;
2626

27+
/// The list of options for executing an fvm message
28+
pub struct ExecutionOptions<F> {
29+
/// Indicates if the execution should always revert regardless the transaction outcome.
30+
/// This is useful for read only executions.
31+
pub always_revert: bool,
32+
/// The hook to handle the transaction gas fee at the end.
33+
/// If this is not provided, a default fvm implementation will be used.
34+
pub txn_gas_hook: F,
35+
}
36+
2737
/// The default [`Executor`].
2838
///
2939
/// # Warning
@@ -112,11 +122,25 @@ where
112122
pub fn execute_message_with_revert(
113123
&mut self,
114124
msg: Message,
115-
mut apply_kind: ApplyKind,
125+
apply_kind: ApplyKind,
116126
raw_length: usize,
117127
always_revert: bool,
118128
) -> anyhow::Result<ApplyRet> {
119-
if always_revert {
129+
let options = ExecutionOptions {
130+
always_revert,
131+
txn_gas_hook: default_gas_hook,
132+
};
133+
self.execute_message_with_options(msg, apply_kind, raw_length, options)
134+
}
135+
136+
pub fn execute_message_with_options<F: TxnGasHook>(
137+
&mut self,
138+
msg: Message,
139+
mut apply_kind: ApplyKind,
140+
raw_length: usize,
141+
options: ExecutionOptions<F>,
142+
) -> anyhow::Result<ApplyRet> {
143+
if options.always_revert {
120144
// The apply kind is always hard coded to implicit if the call is expected to revert.
121145
// This will bypass some checks and gas deduction in `preflight_messages`.
122146
apply_kind = ApplyKind::Implicit;
@@ -213,7 +237,7 @@ where
213237
false,
214238
)
215239
},
216-
always_revert,
240+
options.always_revert,
217241
);
218242

219243
let (res, machine) = match cm.finish() {
@@ -320,14 +344,15 @@ where
320344
};
321345

322346
match apply_kind {
323-
ApplyKind::Explicit => self.finish_message(
347+
ApplyKind::Explicit => self.finish_message_with_hook(
324348
sender_id,
325349
msg,
326350
receipt,
327351
failure_info,
328352
gas_cost,
329353
exec_trace,
330354
events,
355+
options.txn_gas_hook,
331356
),
332357
ApplyKind::Implicit => Ok(ApplyRet {
333358
msg_receipt: receipt,
@@ -484,7 +509,7 @@ where
484509
}
485510

486511
#[allow(clippy::too_many_arguments)]
487-
fn finish_message(
512+
fn finish_message_with_hook<F>(
488513
&mut self,
489514
sender_id: ActorID,
490515
msg: Message,
@@ -493,25 +518,20 @@ where
493518
gas_cost: TokenAmount,
494519
exec_trace: ExecutionTrace,
495520
events: Vec<StampedEvent>,
496-
) -> anyhow::Result<ApplyRet> {
497-
// NOTE: we don't support old network versions in the FVM, so we always burn.
498-
let GasOutputs {
499-
base_fee_burn,
500-
over_estimation_burn,
501-
miner_penalty,
502-
miner_tip,
503-
refund,
504-
gas_refund,
505-
gas_burned,
506-
} = GasOutputs::compute(
521+
gas_hook: F,
522+
) -> anyhow::Result<ApplyRet>
523+
where
524+
F: TxnGasHook,
525+
{
526+
let gas_outputs = GasOutputs::compute(
507527
receipt.gas_used,
508528
msg.gas_limit,
509529
&self.context().base_fee,
510530
&msg.gas_fee_cap,
511531
&msg.gas_premium,
512532
);
513533

514-
let mut transfer_to_actor = |addr: ActorID, amt: &TokenAmount| -> anyhow::Result<()> {
534+
let mut transfer_to_actor = |addr: ActorID, amt: &TokenAmount| {
515535
if amt.is_negative() {
516536
return Err(anyhow!("attempted to transfer negative value into actor"));
517537
}
@@ -525,19 +545,34 @@ where
525545
Ok(())
526546
};
527547

528-
transfer_to_actor(BURNT_FUNDS_ACTOR_ID, &base_fee_burn)?;
548+
let gas_distributions = gas_hook.call(sender_id, &receipt, &gas_outputs);
549+
let gas_sum: TokenAmount = gas_distributions.iter().map(|(_, amount)| amount).sum();
529550

530-
transfer_to_actor(REWARD_ACTOR_ID, &miner_tip)?;
551+
// NOTE: we don't support old network versions in the FVM, so we always burn.
552+
let GasOutputs {
553+
base_fee_burn,
554+
over_estimation_burn,
555+
miner_penalty,
556+
miner_tip,
557+
refund,
558+
gas_refund,
559+
gas_burned,
560+
} = gas_outputs;
531561

532-
transfer_to_actor(BURNT_FUNDS_ACTOR_ID, &over_estimation_burn)?;
562+
if (&base_fee_burn + &over_estimation_burn + &refund + &miner_tip) != gas_sum {
563+
// Sanity check. This could be a fatal error.
564+
return Err(anyhow!("gas processing hook gives out extra gas"));
565+
}
533566

534-
// refund unused gas
535-
transfer_to_actor(sender_id, &refund)?;
567+
for (id, amount) in gas_distributions {
568+
transfer_to_actor(id, &amount)?;
569+
}
536570

537-
if (&base_fee_burn + &over_estimation_burn + &refund + &miner_tip) != gas_cost {
571+
if gas_sum != gas_cost {
538572
// Sanity check. This could be a fatal error.
539-
return Err(anyhow!("Gas handling math is wrong"));
573+
return Err(anyhow!("Txn gas fee hook invalid, expecting total {} but found {}", gas_cost, gas_sum));
540574
}
575+
541576
Ok(ApplyRet {
542577
msg_receipt: receipt,
543578
penalty: miner_penalty,
@@ -569,3 +604,48 @@ where
569604
)
570605
}
571606
}
607+
608+
pub fn default_gas_hook(
609+
sender_id: ActorID,
610+
_: &Receipt,
611+
gas_output: &GasOutputs,
612+
) -> Vec<(ActorID, TokenAmount)> {
613+
vec![
614+
(BURNT_FUNDS_ACTOR_ID, gas_output.base_fee_burn.clone()),
615+
(REWARD_ACTOR_ID, gas_output.miner_tip.clone()),
616+
(
617+
BURNT_FUNDS_ACTOR_ID,
618+
gas_output.over_estimation_burn.clone(),
619+
),
620+
(sender_id, gas_output.refund.clone()),
621+
]
622+
}
623+
624+
/// The gas hook to handle how to transfer the final txn gas.
625+
/// Returns a vec of the amount of gas that should be deposited into each actor id
626+
pub trait TxnGasHook {
627+
fn call(
628+
self,
629+
// the sender of the txn
630+
actor_id: ActorID,
631+
// the transaction outcome
632+
receipt: &Receipt,
633+
// the gas usage and distribution according to fvm rules
634+
gas_outputs: &GasOutputs,
635+
) -> Vec<(ActorID, TokenAmount)>;
636+
}
637+
638+
// Implement the trait for any function/closure that matches the signature
639+
impl<F> TxnGasHook for F
640+
where
641+
F: FnOnce(ActorID, &Receipt, &GasOutputs) -> Vec<(ActorID, TokenAmount)>,
642+
{
643+
fn call(
644+
self,
645+
actor_id: ActorID,
646+
receipt: &Receipt,
647+
gas_outputs: &GasOutputs,
648+
) -> Vec<(ActorID, TokenAmount)> {
649+
self(actor_id, receipt, gas_outputs)
650+
}
651+
}

fvm/src/executor/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use crate::Kernel;
2020
use crate::call_manager::Backtrace;
2121
use crate::trace::ExecutionTrace;
2222

23+
pub use default::{default_gas_hook, ExecutionOptions, TxnGasHook};
24+
2325
/// An executor executes messages on the underlying machine/kernel. It's responsible for:
2426
///
2527
/// 1. Validating messages (nonce, sender, etc).

fvm/src/gas/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use num_traits::Zero;
1111

1212
pub use self::charge::GasCharge;
1313
pub use self::outputs::GasOutputs;
14-
pub use self::price_list::{PriceList, WasmGasPrices, price_list_by_network_version};
14+
pub use self::price_list::{price_list_by_network_version, PriceList, WasmGasPrices};
1515
pub use self::timer::{GasDuration, GasInstant, GasTimer};
1616
use crate::kernel::{ClassifyResult, ExecutionError, Result};
1717

0 commit comments

Comments
 (0)