Skip to content

Commit

Permalink
Converting to a better rendition of the state machine given a new und…
Browse files Browse the repository at this point in the history
…erstanding of things after reviewing things with adam. I had remembered the update part but not the timelock of the unroll coin, which must be allowed to expire before the party who decides to go on chain spends it (and must be routed into UnrollCoin). That's what gives the other party time to do their upgrade if desired. New potato handler states are needed to support this timing and have been added. The new workflow supports this.
  • Loading branch information
prozacchiwawa committed Nov 14, 2024
1 parent 4844ccc commit 873fc26
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 85 deletions.
1 change: 1 addition & 0 deletions src/channel_handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub struct ChannelHandler {

have_potato: bool,
initiated_on_chain: bool,
// Specifies the time lock that should be used in the unroll coin's conditions.
unroll_advance_timeout: Timeout,

cached_last_action: Option<CachedPotatoRegenerateLastHop>,
Expand Down
2 changes: 2 additions & 0 deletions src/channel_handler/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,8 @@ pub struct UnrollCoinOutcome {
/// The unrolling player will have to trigger the "reveal" part as below after a time
/// if the other player doesn't successfully challenge by providing another program that
/// produces new conditions that match the parity criteria.
///
/// XXX TODO: Add time lock
#[derive(Default, Clone)]
pub struct UnrollCoin {
pub started_with_potato: bool,
Expand Down
4 changes: 2 additions & 2 deletions src/peer_container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ pub trait GameCradle {
allocator: &mut AllocEncoder,
rng: &mut R,
local_ui: &mut dyn ToLocalUI,
) -> Result<Vec<OnChainGameCoin>, Error>;
) -> Result<(), Error>;

/// Report a puzzle and solution for a spent coin.
fn report_puzzle_and_solution<R: Rng>(
Expand Down Expand Up @@ -820,7 +820,7 @@ impl GameCradle for SynchronousGameCradle {
allocator: &mut AllocEncoder,
rng: &mut R,
_local_ui: &mut dyn ToLocalUI,
) -> Result<Vec<OnChainGameCoin>, Error> {
) -> Result<(), Error> {
let mut env = channel_handler_env(allocator, rng);
let mut penv: SynchronousGamePeerEnv<R> = SynchronousGamePeerEnv {
env: &mut env,
Expand Down
184 changes: 101 additions & 83 deletions src/potato_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ pub enum HandshakeState {
// Other party went on chain, we're catching up route.
OnChainWaitForConditions(CoinString, Box<HandshakeStepWithSpend>),
// Converge here to on chain state.
OnChainWaitingForUnrollSpend(CoinString),
OnChain(CoinString, Box<HandshakeStepWithSpend>),
WaitingForShutdown(CoinString, CoinString),
Completed,
Expand Down Expand Up @@ -1470,18 +1471,7 @@ impl PotatoHandler {
match hs {
HandshakeState::OnChainTransition(cs, t) => {
debug!("notified of channel coin spend in on chain transition state");
// We must wait for the unroll coin to time out or be spent.
// First find the unroll coin id.
let ch = self.channel_handler()?;
let (env, _) = penv.env();
let use_unroll = ch.get_unroll_coin();
let puzzle = use_unroll.coin.make_curried_unroll_puzzle(
env,
&ch.get_aggregate_unroll_public_key()
)?;
let puzzle_hash = Node(puzzle).sha256tree(env.allocator);
let amt =
self.handshake_state = HandshakeState::OnChainWaitingForUnrollTimeoutOrSpend(cs, t);
return Ok(true);
}
HandshakeState::Finished(hs) => {
debug!("notified of channel coin spend in run state");
Expand All @@ -1505,26 +1495,87 @@ impl PotatoHandler {
Ok(false)
}

pub fn do_unroll_spend_to_games<'a, G, R: Rng + 'a>(
pub fn do_channel_spend_to_unroll<'a, G, R: Rng + 'a>(
&mut self,
penv: &mut dyn PeerEnv<'a, G, R>,
_channel_coin: &CoinString,
_unroll_puzzle_hash: &PuzzleHash,
_amount: &Amount,
unroll_coin: &CoinString,
) -> Result<Vec<OnChainGameCoin>, Error>
spend: Box<HandshakeStepWithSpend>,
) -> Result<(), Error>
where
G: ToLocalUI + BootstrapTowardWallet + WalletSpendInterface + PacketSender + 'a,
{
let mut spend = match &self.handshake_state {
HandshakeState::Finished(with_spend) => with_spend.spend.clone(),
_ => {
return Err(Error::StrErr(
"unroll spend to games before handshake".to_string(),
));
}
let (env, system_interface) = penv.env();
let player_ch = self.channel_handler()?;
// Channel coin
let finished_unroll_coin = player_ch.get_unroll_coin();
let pre_unroll_data = player_ch.get_create_unroll_coin_transaction(env, &finished_unroll_coin, false)?;
debug!(
"channel unroll to on chain puzzle: {}",
pre_unroll_data.transaction.puzzle.to_hex()
);
debug!(
"unroll to on chain solution: {}",
pre_unroll_data.transaction.solution.to_hex()
);

let run_puzzle = pre_unroll_data
.transaction
.puzzle
.to_clvm(env.allocator)
.into_gen()?;
let run_args = pre_unroll_data
.transaction
.solution
.to_clvm(env.allocator)
.into_gen()?;
let puzzle_result = run_program(
env.allocator.allocator(),
&chia_dialect(),
run_puzzle,
run_args,
0,
)
.into_gen()?;
let condition_list = CoinCondition::from_nodeptr(env.allocator, puzzle_result.1);
let unroll_result = if let Some(unroll_coin) = condition_list
.iter()
.filter_map(|cond| {
if let CoinCondition::CreateCoin(ph, amt) = cond {
if *amt > Amount::default() {
return Some(CoinString::from_parts(&player_ch.state_channel_coin().to_coin_id(), ph, amt));
}
}

None
})
.next()
{
unroll_coin.clone()
} else {
return Err(Error::StrErr("no unroll coin created".to_string()));
};

// We have everything needed so let's register for the spend
self.handshake_state =
HandshakeState::OnChainWaitingForUnrollTimeoutOrSpend(unroll_result.clone(), spend);

// We'll wait for the unroll result to be spent, which means we're on chain.
system_interface.register_coin(&unroll_result, &self.channel_timeout)?;
// The coin outputs represent the ongoing games if any and the reward coins.
let ch = self.channel_handler_mut()?;
let coins = ch.get_game_coins(env)?;
debug!("game coins {coins:?}");

Ok(())
}

pub fn do_unroll_spend_to_games<'a, G, R: Rng + 'a>(
&mut self,
penv: &mut dyn PeerEnv<'a, G, R>,
unroll_coin: &CoinString,
) -> Result<(), Error>
where
G: ToLocalUI + BootstrapTowardWallet + WalletSpendInterface + PacketSender + 'a,
{
let (env, system_interface) = penv.env();
let player_ch = self.channel_handler()?;
// Channel coin
Expand Down Expand Up @@ -1577,40 +1628,20 @@ impl PotatoHandler {
return Err(Error::StrErr("no unroll coin created".to_string()));
};

let unroll_spend = CoinSpend {
bundle: pre_unroll_data.transaction.clone(),
coin: unroll_coin.clone(),
};
// spend.spends.push(unroll_spend);

system_interface.spend_transaction_and_add_fee(&spend)?;

let mut hs = HandshakeState::StepA;
swap(&mut hs, &mut self.handshake_state);
match hs {
HandshakeState::Finished(with_spend) => {
let (env, system_interface) = penv.env();
// We have everything needed so let's register for the spend
self.handshake_state =
HandshakeState::OnChainTransition(unroll_result.clone(), with_spend.clone());
// We'll wait for the unroll result to be spent, which means we're on chain.
system_interface.register_coin(&unroll_result, &self.channel_timeout)?;
// The coin outputs represent the ongoing games if any and the reward coins.
let ch = self.channel_handler_mut()?;
let coins = ch.get_game_coins(env)?;
debug!("game coins {coins:?}");
system_interface.spend_transaction_and_add_fee(&SpendBundle {
spends: vec![CoinSpend {
bundle: pre_unroll_data.transaction.clone(),
coin: unroll_coin.clone(),
}]
})?;

ch.set_state_for_coins(env, &coins)?;
// We have everything needed so let's register for the spend
self.handshake_state =
HandshakeState::OnChainWaitingForUnrollSpend(unroll_result.clone());
// We'll wait for the unroll result to be spent, which means we're on chain.
system_interface.register_coin(&unroll_result, &self.channel_timeout)?;

Ok(coins)
}
hs => {
self.handshake_state = hs;
Err(Error::StrErr(
"tried to go on chain without handshake".to_string(),
))
}
}
Ok(())
}

/// Short circuit to go on chain.
Expand All @@ -1622,37 +1653,20 @@ impl PotatoHandler {
pub fn go_on_chain<'a, G, R: Rng + 'a>(
&mut self,
penv: &mut dyn PeerEnv<'a, G, R>,
) -> Result<Vec<OnChainGameCoin>, Error>
) -> Result<(), Error>
where
G: ToLocalUI + BootstrapTowardWallet + WalletSpendInterface + PacketSender + 'a,
{
let ch = self.channel_handler()?;
let unroll_target = if let HandshakeState::Finished(_hs) = &self.handshake_state {
let (env, _) = penv.env();
let use_unroll = ch.get_unroll_coin();
ch.get_unroll_target(env, use_unroll)?
let mut hs_state = HandshakeState::StepA;
swap(&mut hs_state, &mut self.handshake_state);
if let HandshakeState::Finished(t) = hs_state {
self.do_channel_spend_to_unroll(penv, t)
} else {
return Err(Error::StrErr(
self.handshake_state = hs_state;
Err(Error::StrErr(
"go on chain before handshake finished".to_string(),
));
};

let channel_coin = ch.state_channel_coin().coin_string();
debug!("the channel_coin is {channel_coin:?}");
let amount = unroll_target.my_amount + unroll_target.their_amount;
let unroll_coin = CoinString::from_parts(
&channel_coin.to_coin_id(),
&unroll_target.unroll_puzzle_hash,
&amount,
);
debug!("the unroll_coin is {unroll_coin:?}");
self.do_unroll_spend_to_games(
penv,
&channel_coin.clone(),
&unroll_target.unroll_puzzle_hash,
&amount,
&unroll_coin,
)
))
}
}

fn do_game_action<'a, G, R: Rng + 'a>(
Expand Down Expand Up @@ -1976,6 +1990,10 @@ impl<G: ToLocalUI + BootstrapTowardWallet + WalletSpendInterface + PacketSender,
G: 'a,
R: 'a,
{
// We should be in state OnChainWaitingForUnrollTimeoutOrSpend
// We'll spend the unroll coin via do_unroll_spend_to_games with the default
// reveal and go to OnChainWaitingForUnrollSpend, transitioning to OnChain when
// we receive the unroll coin spend.
todo!();
}

Expand Down

0 comments on commit 873fc26

Please sign in to comment.