Skip to content

Commit

Permalink
Paych actor updates (#608)
Browse files Browse the repository at this point in the history
* update resolve account

* remove todo

* Update state

* Update update_channel_state

* update settle

* update collect and cleanup

* Wip fixing tests

* Fix other tests

* Add commit tag on changes

* Swap order of add to avoid clone
  • Loading branch information
austinabell authored Aug 7, 2020
1 parent 9c8e9a6 commit 4aeadf7
Show file tree
Hide file tree
Showing 5 changed files with 383 additions and 371 deletions.
195 changes: 84 additions & 111 deletions vm/actor/src/builtin/paych/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ pub use self::state::{LaneState, Merge, State};
pub use self::types::*;
use crate::{check_empty_params, ACCOUNT_ACTOR_CODE_ID, INIT_ACTOR_CODE_ID};
use address::Address;
use encoding::to_vec;
use ipld_blockstore::BlockStore;
use num_bigint::BigInt;
use num_bigint::{BigInt, Sign};
use num_derive::FromPrimitive;
use num_traits::{FromPrimitive, Zero};
use runtime::{ActorCode, Runtime};
Expand All @@ -19,6 +18,8 @@ use vm::{
METHOD_SEND,
};

// * Updated to specs-actors commit: 2a207366baa881a599fe246dc7862eaa774be2f8 (0.8.6)

/// Payment Channel actor methods available
#[derive(FromPrimitive)]
#[repr(u64)]
Expand All @@ -43,11 +44,12 @@ impl Actor {
rt.validate_immediate_caller_type(std::iter::once(&*INIT_ACTOR_CODE_ID))?;

// Check both parties are capable of signing vouchers
// TODO This was indicated as incorrect, fix when updated on specs-actors
let to = Self::resolve_account(rt, &params.to)
.map_err(|e| actor_error!(ErrIllegalArgument; e))?;
.map_err(|e| actor_error!(ErrIllegalArgument; e.msg()))?;

let from = Self::resolve_account(rt, &params.from)
.map_err(|e| actor_error!(ErrIllegalArgument; e))?;
.map_err(|e| actor_error!(ErrIllegalArgument; e.msg()))?;

rt.create(&State::new(from, to))?;
Ok(())
Expand All @@ -56,27 +58,25 @@ impl Actor {
/// Resolves an address to a canonical ID address and requires it to address an account actor.
/// The account actor constructor checks that the embedded address is associated with an appropriate key.
/// An alternative (more expensive) would be to send a message to the actor to fetch its key.
fn resolve_account<BS, RT>(rt: &RT, raw: &Address) -> Result<Address, String>
fn resolve_account<BS, RT>(rt: &RT, raw: &Address) -> Result<Address, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let resolved = rt
// TODO: fatal error not handled here. To match go this will have to be refactored
.resolve_address(raw)
.map_err(|e| e.to_string())?
.ok_or_else(|| format!("failed to resolve address {}", raw))?;
.resolve_address(raw)?
.ok_or_else(|| actor_error!(ErrNotFound; "failed to resolve address {}", raw))?;

let code_cid = rt
.get_actor_code_cid(&resolved)
.expect("Failed to get code Cid")
.ok_or_else(|| format!("no code for address {}", raw))?;
.get_actor_code_cid(&resolved)?
.ok_or_else(|| actor_error!(ErrIllegalState; "no code for address {}", raw))?;

if code_cid != *ACCOUNT_ACTOR_CODE_ID {
Err(format!(
"actor {} must be an account ({}), was {}",
raw, &*ACCOUNT_ACTOR_CODE_ID, code_cid
))
Err(
actor_error!(ErrForbidden; "actor {} must be an account ({}), was {}",
raw, *ACCOUNT_ACTOR_CODE_ID, code_cid
),
)
} else {
Ok(resolved)
}
Expand All @@ -98,38 +98,47 @@ impl Actor {
} else {
st.from
};

let sv = params.sv;

// Pull signature from signed voucher
let sig = sv
.signature
.as_ref()
.ok_or_else(|| rt.abort(ExitCode::ErrIllegalArgument, "voucher has no signature"))?;
.ok_or_else(|| actor_error!(ErrIllegalArgument; "voucher has no signature"))?;

// Generate unsigned bytes
let sv_bz = to_vec(&sv).map_err(|_| {
rt.abort(
ExitCode::ErrIllegalArgument,
"failed to serialize SignedVoucher",
)
})?;
let sv_bz = sv.signing_bytes().map_err(
|e| actor_error!(ErrIllegalArgument; "failed to serialized SignedVoucher: {}", e),
)?;

// Validate signature
rt.syscalls()
.verify_signature(&sig, &signer, &sv_bz)
.map_err(|e| {
ActorError::new(
ExitCode::ErrIllegalArgument,
format!("voucher signature invalid: {}", e),
)
.map_err(|e| match e.downcast::<ActorError>() {
Ok(actor_err) => *actor_err,
Err(other) => {
actor_error!(ErrIllegalArgument; "voucher signature invalid: {}", other)
}
})?;

let pch_addr = rt.message().receiver();
if pch_addr != &sv.channel_addr {
return Err(actor_error!(ErrIllegalArgument;
"voucher payment channel address {} does not match receiver {}",
sv.channel_addr, pch_addr));
}

if rt.curr_epoch() < sv.time_lock_min {
return Err(rt.abort(ExitCode::ErrIllegalArgument, "cannot use this voucher yet"));
return Err(actor_error!(ErrIllegalArgument; "cannot use this voucher yet"));
}

if sv.time_lock_max != 0 && rt.curr_epoch() > sv.time_lock_max {
return Err(rt.abort(ExitCode::ErrIllegalArgument, "this voucher has expired"));
return Err(actor_error!(ErrIllegalArgument; "this voucher has expired"));
}

if sv.amount.sign() == Sign::Minus {
return Err(actor_error!(ErrIllegalArgument;
"voucher amount must be non-negative, was {}", sv.amount));
}

if !sv.secret_pre_image.is_empty() {
Expand All @@ -138,10 +147,7 @@ impl Actor {
.hash_blake2b(&params.secret)
.map_err(|e| *e.downcast::<ActorError>().unwrap())?;
if hashed_secret != sv.secret_pre_image.as_slice() {
return Err(ActorError::new(
ExitCode::ErrIllegalArgument,
"incorrect secret".to_owned(),
));
return Err(actor_error!(ErrIllegalArgument; "incorrect secret"));
}
}

Expand All @@ -154,62 +160,55 @@ impl Actor {
proof: params.proof,
})?,
TokenAmount::from(0u8),
)?;
)
.map_err(|e| e.wrap("spend voucher verification failed"))?;
}

let curr_bal = rt.current_balance()?;
rt.transaction(|st: &mut State, _| {
let mut lane_found = true;
// Find the voucher lane, create and insert it in sorted order if necessary.
let (idx, exists) = find_lane(&st.lane_states, sv.lane);
if !exists {
if st.lane_states.len() >= LANE_LIMIT {
return Err(ActorError::new(
ExitCode::ErrIllegalArgument,
"lane limit exceeded".to_owned(),
));
return Err(actor_error!(ErrIllegalArgument; "lane limit exceeded"));
}
let tmp_ls = LaneState {
id: sv.lane,
redeemed: BigInt::zero(),
nonce: 0,
};
st.lane_states.insert(idx, tmp_ls);
lane_found = false;
};
// let mut ls = st.lane_states[idx].clone();

if st.lane_states[idx].nonce > sv.nonce {
return Err(ActorError::new(
ExitCode::ErrIllegalArgument,
"voucher has an outdated nonce, cannot redeem".to_owned(),
));
if lane_found && st.lane_states[idx].nonce >= sv.nonce {
return Err(actor_error!(ErrIllegalArgument;
"voucher has an outdated nonce, existing: {}, voucher: {}, cannot redeem",
st.lane_states[idx].nonce, sv.nonce));
}

// The next section actually calculates the payment amounts to update the payment channel state
// The next section actually calculates the payment amounts to update
// the payment channel state
// 1. (optional) sum already redeemed value of all merging lanes
let mut redeemed = BigInt::default();
for merge in sv.merges {
if merge.lane == sv.lane {
return Err(ActorError::new(
ExitCode::ErrIllegalArgument,
"voucher cannot merge lanes into it's own lane".to_owned(),
));
return Err(actor_error!(ErrIllegalArgument;
"voucher cannot merge lanes into it's own lane".to_owned()));
}
let (idx, exists) = find_lane(&st.lane_states, merge.lane);
let (other_idx, exists) = find_lane(&st.lane_states, merge.lane);
if exists {
if st.lane_states[idx].nonce >= merge.nonce {
return Err(ActorError::new(
ExitCode::ErrIllegalArgument,
"merged lane in voucher has outdated nonce, cannot redeem".to_owned(),
));
if st.lane_states[other_idx].nonce >= merge.nonce {
return Err(actor_error!(ErrIllegalArgument;
"merged lane in voucher has outdated nonce, cannot redeem"));
}

redeemed += &st.lane_states[idx].redeemed;
st.lane_states[idx].nonce = merge.nonce;
redeemed += &st.lane_states[other_idx].redeemed;
st.lane_states[other_idx].nonce = merge.nonce;
} else {
return Err(ActorError::new(
ExitCode::ErrIllegalArgument,
format!("voucher specifies invalid merge lane {}", merge.lane),
));
return Err(actor_error!(ErrIllegalArgument;
"voucher specifies invalid merge lane {}", merge.lane));
}
}

Expand All @@ -222,20 +221,16 @@ impl Actor {
st.lane_states[idx].redeemed = sv.amount;

// 4. check operation validity
let new_send_balance = st.to_send.clone() + balance_delta;
let new_send_balance = balance_delta + &st.to_send;

if new_send_balance < TokenAmount::from(0u8) {
return Err(ActorError::new(
ExitCode::ErrIllegalState,
"voucher would leave channel balance negative".to_owned(),
));
if new_send_balance < TokenAmount::from(0) {
return Err(actor_error!(ErrIllegalArgument;
"voucher would leave channel balance negative"));
}

if new_send_balance > curr_bal {
return Err(ActorError::new(
ExitCode::ErrIllegalState,
"not enough funds in channel to cover voucher".to_owned(),
));
return Err(actor_error!(ErrIllegalArgument;
"not enough funds in channel to cover voucher"));
}

// 5. add new redemption ToSend
Expand All @@ -259,19 +254,14 @@ impl Actor {
BS: BlockStore,
RT: Runtime<BS>,
{
let epoch = rt.curr_epoch();
let st: State = rt.state()?;
rt.validate_immediate_caller_is([st.from, st.to].iter())?;
rt.transaction(|st: &mut State, rt| {
rt.validate_immediate_caller_is([st.from, st.to].iter())?;

rt.transaction(|st: &mut State, _| {
if st.settling_at != 0 {
return Err(ActorError::new(
ExitCode::ErrIllegalState,
"channel already settling".to_owned(),
));
return Err(actor_error!(ErrIllegalState; "channel already settling"));
}

st.settling_at = epoch + SETTLE_DELAY;
st.settling_at = rt.curr_epoch() + SETTLE_DELAY;
if st.settling_at < st.min_settle_height {
st.settling_at = st.min_settle_height;
}
Expand All @@ -289,34 +279,17 @@ impl Actor {
rt.validate_immediate_caller_is(&[st.from, st.to])?;

if st.settling_at == 0 || rt.curr_epoch() < st.settling_at {
return Err(rt.abort(
ExitCode::ErrForbidden,
"payment channel not settling or settled",
));
return Err(actor_error!(ErrForbidden; "payment channel not settling or settled"));
}

// TODO revisit: Spec doesn't check this, could be possible balance is below to_send?
let rem_bal = rt
.current_balance()?
.checked_sub(&st.to_send)
.ok_or_else(|| {
rt.abort(
ExitCode::ErrInsufficientFunds,
"Cannot send more than remaining balance",
)
})?;

// send remaining balance to `from`
rt.send(st.from, METHOD_SEND, Serialized::default(), rem_bal)?;

// send ToSend to `to`
rt.send(st.to, METHOD_SEND, Serialized::default(), st.to_send)?;
rt.send(st.to, METHOD_SEND, Serialized::default(), st.to_send)
.map_err(|e| e.wrap("Failed to send funds to `to` address: "))?;

rt.transaction(|st: &mut State, _| {
st.to_send = TokenAmount::from(0u8);
// the remaining balance will be returned to "From" upon deletion.
rt.delete_actor(&st.from)?;

Ok(())
})?
Ok(())
}
}

Expand All @@ -341,7 +314,11 @@ impl ActorCode for Actor {
{
match FromPrimitive::from_u64(method) {
Some(Method::Constructor) => {
Self::constructor(rt, params.deserialize().unwrap())?;
Self::constructor(rt, params.deserialize()?)?;
Ok(Serialized::default())
}
Some(Method::UpdateChannelState) => {
Self::update_channel_state(rt, params.deserialize()?)?;
Ok(Serialized::default())
}
Some(Method::Settle) => {
Expand All @@ -354,11 +331,7 @@ impl ActorCode for Actor {
Self::collect(rt)?;
Ok(Serialized::default())
}
Some(Method::UpdateChannelState) => {
Self::update_channel_state(rt, params.deserialize()?)?;
Ok(Serialized::default())
}
_ => Err(rt.abort(ExitCode::SysErrInvalidMethod, "Invalid method")),
_ => Err(actor_error!(SysErrInvalidMethod; "Invalid method")),
}
}
}
1 change: 0 additions & 1 deletion vm/actor/src/builtin/paych/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ impl State {
pub struct LaneState {
/// Identifier unique to this channel
pub id: u64,
// TODO this could possibly be a BigUint, but won't affect serialization
#[serde(with = "bigint_ser")]
pub redeemed: BigInt,
pub nonce: u64,
Expand Down
Loading

0 comments on commit 4aeadf7

Please sign in to comment.