use crate::{
asm::Op,
error::{OpAsyncError, OpError, OutOfGasError, StateReadError},
state_read::{self, StateReadFuture},
step_op_sync, Access, ContentAddress, Gas, GasLimit, OpAccess, OpAsync, OpAsyncResult,
OpGasCost, OpKind, StateRead, Vm,
};
use core::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
pub struct ExecFuture<'a, S, OA, OG>
where
S: StateRead,
{
access: Access<'a>,
state_read: &'a S,
op_access: OA,
op_gas_cost: &'a OG,
vm: Option<&'a mut Vm>,
gas: GasExec,
pending_op: Option<PendingOp<'a, S>>,
}
struct GasExec {
limit: GasLimit,
next_yield_threshold: Gas,
spent: Gas,
}
struct PendingOp<'a, S>
where
S: StateRead,
{
future: StepOpAsyncFuture<'a, S>,
next_spent: Gas,
}
enum StepOpAsyncFuture<'a, S>
where
S: StateRead,
{
StateRead(StateReadFuture<'a, S>),
}
impl From<GasLimit> for GasExec {
fn from(limit: GasLimit) -> Self {
GasExec {
spent: 0,
next_yield_threshold: limit.per_yield,
limit,
}
}
}
impl<'a, S> From<StepOpAsyncFuture<'a, S>> for &'a mut Vm
where
S: StateRead,
{
fn from(future: StepOpAsyncFuture<'a, S>) -> Self {
match future {
StepOpAsyncFuture::StateRead(future) => future.vm,
}
}
}
impl<'a, S, OA, OG> Future for ExecFuture<'a, S, OA, OG>
where
S: StateRead,
OA: OpAccess<Op = Op> + Unpin,
OG: OpGasCost,
OA::Error: Into<OpError<S::Error>>,
{
type Output = Result<Gas, StateReadError<S::Error>>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let vm = match self.pending_op.as_mut() {
None => self.vm.take().expect("future polled after completion"),
Some(pending) => {
let res = match Pin::new(&mut pending.future).poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(ready) => ready,
};
let pending = self.pending_op.take().expect("guaranteed `Some`");
let next_spent = pending.next_spent;
let vm: &'a mut Vm = pending.future.into();
#[cfg(feature = "tracing")]
trace_op_res(&mut self.op_access, &*vm, res.as_ref());
match res {
Ok(new_pc) => vm.pc = new_pc,
Err(err) => {
let err = StateReadError::Op(vm.pc, err.into());
return Poll::Ready(Err(err));
}
};
self.gas.spent = next_spent;
self.gas.next_yield_threshold =
self.gas.spent.saturating_add(self.gas.limit.per_yield);
vm
}
};
while let Some(res) = self.op_access.op_access(vm.pc) {
let op = match res {
Ok(op) => op,
Err(err) => {
let err = StateReadError::Op(vm.pc, err.into());
return Poll::Ready(Err(err));
}
};
let op_gas = self.op_gas_cost.op_gas_cost(&op);
let next_spent = match self
.gas
.spent
.checked_add(op_gas)
.filter(|&spent| spent <= self.gas.limit.total)
.ok_or_else(|| out_of_gas(&self.gas, op_gas))
.map_err(|err| StateReadError::Op(vm.pc, err.into()))
{
Err(err) => return Poll::Ready(Err(err)),
Ok(next_spent) => next_spent,
};
let res = match OpKind::from(op) {
OpKind::Sync(op) => step_op_sync(op, self.access, vm),
OpKind::Async(op) => {
let contract_addr = self
.access
.solution
.this_data()
.predicate_to_solve
.contract
.clone();
let pc = vm.pc;
let future = match step_op_async(op, contract_addr, self.state_read, vm) {
Err(err) => {
let err = StateReadError::Op(pc, err.into());
return Poll::Ready(Err(err));
}
Ok(fut) => fut,
};
self.pending_op = Some(PendingOp { future, next_spent });
cx.waker().wake_by_ref();
return Poll::Pending;
}
};
#[cfg(feature = "tracing")]
trace_op_res(&mut self.op_access, &*vm, res.as_ref());
let opt_new_pc = match res {
Ok(opt) => opt,
Err(err) => {
return Poll::Ready(Err(StateReadError::Op(vm.pc, err.into())));
}
};
self.gas.spent = next_spent;
match opt_new_pc {
Some(new_pc) => vm.pc = new_pc,
None => return Poll::Ready(Ok(self.gas.spent)),
}
if self.gas.next_yield_threshold <= self.gas.spent {
self.gas.next_yield_threshold =
self.gas.spent.saturating_add(self.gas.limit.per_yield);
self.vm = Some(vm);
cx.waker().wake_by_ref();
return Poll::Pending;
}
}
Poll::Ready(Ok(self.gas.spent))
}
}
impl<'vm, S> Future for StepOpAsyncFuture<'vm, S>
where
S: StateRead,
{
type Output = OpAsyncResult<usize, S::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
let (prev_pc, res) = match *self {
Self::StateRead(ref mut future) => {
let pc = future.vm.pc;
match Pin::new(future).poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(res) => (pc, res),
}
}
};
let new_pc = prev_pc.checked_add(1).ok_or(OpAsyncError::PcOverflow)?;
let res = res.map(|()| new_pc);
Poll::Ready(res)
}
}
pub(crate) fn exec<'a, S, OA, OG>(
vm: &'a mut Vm,
access: Access<'a>,
state_read: &'a S,
op_access: OA,
op_gas_cost: &'a OG,
gas_limit: GasLimit,
) -> ExecFuture<'a, S, OA, OG>
where
S: StateRead,
OA: OpAccess<Op = Op> + Unpin,
OG: OpGasCost,
OA::Error: Into<OpError<S::Error>>,
{
ExecFuture {
access,
state_read,
op_access,
op_gas_cost,
vm: Some(vm),
gas: GasExec::from(gas_limit),
pending_op: None,
}
}
fn step_op_async<'a, S>(
op: OpAsync,
contract_addr: ContentAddress,
state_read: &'a S,
vm: &'a mut Vm,
) -> OpAsyncResult<StepOpAsyncFuture<'a, S>, S::Error>
where
S: StateRead,
{
match op {
OpAsync::StateReadKeyRange => {
let future = state_read::key_range(state_read, &contract_addr, &mut *vm)?;
Ok(StepOpAsyncFuture::StateRead(future))
}
OpAsync::StateReadKeyRangeExt => {
let future = state_read::key_range_ext(state_read, &mut *vm)?;
Ok(StepOpAsyncFuture::StateRead(future))
}
}
}
fn out_of_gas(exec: &GasExec, op_gas: Gas) -> OutOfGasError {
OutOfGasError {
spent: exec.spent,
limit: exec.limit.total,
op_gas,
}
}
#[cfg(feature = "tracing")]
fn trace_op_res<OA, T, E>(oa: &mut OA, vm: &Vm, op_res: Result<T, E>)
where
OA: OpAccess,
OA::Op: core::fmt::Debug,
E: core::fmt::Display,
{
let op = oa
.op_access(vm.pc)
.expect("must exist as retrieved previously")
.expect("must exist as retrieved previously");
let pc_op = format!("0x{:02X}: {op:?}", vm.pc);
match op_res {
Ok(_) => {
tracing::trace!(
"{pc_op}\n ├── {:?}\n └── {:?}",
&vm.stack,
&vm.state_memory
)
}
Err(ref err) => {
tracing::trace!("{pc_op}");
tracing::debug!("{err}");
}
}
}