Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
76141cd
feat: add settle_version field in game_context
lxedge May 26, 2025
af484ad
Refactor Checkpoint into Checkpoint and Settle
May 26, 2025
3ce04a3
fix: update pending settles subgame versioned data after recv bridge …
lxedge May 26, 2025
50eff65
fix: validator can not start up
lxedge May 27, 2025
7c3be66
dev: add mtt-multi layout
lxedge May 29, 2025
aba81b7
fix: avoid handle event when recv bridge event in validator mode
lxedge Jun 1, 2025
50b0588
chore: add event into versioned data
lxedge Jun 12, 2025
bc7a191
chore: change settle lock struct
lxedge Jun 12, 2025
e1e3c93
chore: add dispatch and bridge_events into versioned_data
lxedge Jun 13, 2025
53a9f0e
chore: remove event in apply_effect
lxedge Jun 13, 2025
144bbcb
chore: remove some debug log
lxedge Jun 13, 2025
aaa292d
test: add test for apply_effect, assert bridge_events and dispatch
lxedge Jun 13, 2025
55fcf07
test: recover from checkpoint wip
lxedge Jun 14, 2025
d38ca6c
fix: recover from checkpoint
lxedge Jun 18, 2025
f5cdc15
Merge branch 'master' into feature-settle-version-1
lxedge Jun 18, 2025
0a98e49
refactor remove_settle_lock to handle_versioned_data
Jun 19, 2025
a0593b4
Add HandlerT
Jun 20, 2025
400627d
fix: one step to solve restart transactor
lxedge Jun 20, 2025
c209fcf
Fix tests
Jun 20, 2025
56aa836
feat: add subgame shutdown
lxedge Jun 21, 2025
902031d
fix: lack subgame shutdown handle
lxedge Jun 21, 2025
b44e607
Remove Ready event
Jun 23, 2025
be6a93b
fix: can not restart before subgame start, due to dispatch event is None
lxedge Jun 23, 2025
2f8ebf7
fix: wrong settle details pushed into pending vec
lxedge Jun 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion api/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
use std::collections::HashMap;

use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::{
engine::GameHandler,
error::{HandleError, HandleResult},
event::BridgeEvent,
prelude::InitAccount,
random::RandomSpec,
types::{Award, DecisionId, EntryLock, GameDeposit, GameId, GamePlayer, PlayerBalance, RandomId, Transfer},
types::{
Award, DecisionId, EntryLock, GameDeposit, GameId, GamePlayer, PlayerBalance, RandomId,
Transfer,
},
};

#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -55,6 +60,7 @@ pub struct ActionTimeout {
}

#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SubGame {
pub id: GameId,
pub bundle_addr: String,
Expand Down Expand Up @@ -92,6 +98,7 @@ impl SubGame {
}

#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EmitBridgeEvent {
pub dest: GameId,
pub raw: Vec<u8>,
Expand All @@ -104,6 +111,13 @@ impl EmitBridgeEvent {
raw: borsh::to_vec(&bridge_event)?,
})
}

pub fn new_empty(dest: GameId) -> Self {
Self {
dest,
raw: vec![]
}
}
}

#[derive(BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Clone)]
Expand Down
2 changes: 1 addition & 1 deletion api/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{effect::Effect, error::HandleResult, event::Event, init_account::Ini

pub trait GameHandler: Sized + BorshSerialize + BorshDeserialize {
/// Initialize handler state with on-chain game account data.
fn init_state(init_account: InitAccount) -> HandleResult<Self>;
fn init_state(effect: &mut Effect, init_account: InitAccount) -> HandleResult<Self>;

/// Handle event.
fn handle_event(&mut self, effect: &mut Effect, event: Event) -> HandleResult<()>;
Expand Down
5 changes: 0 additions & 5 deletions api/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ pub enum Event {
raw: Vec<u8>,
},

/// A event sent by system, the first event sent by transactor
/// when game is loaded.
Ready,

/// Transactor shares its secert to specific player.
/// The `secret_data` is encrypted with the receiver's public key.
ShareSecrets {
Expand Down Expand Up @@ -159,7 +155,6 @@ impl std::fmt::Display for Event {
dest_game_id, from_game_id, raw[0]
)
}
Event::Ready => write!(f, "Ready"),
Event::ShareSecrets { sender, shares } => {
let repr = shares
.iter()
Expand Down
3 changes: 3 additions & 0 deletions api/src/init_account.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::error::HandleError;

/// A set of arguments for game handler initialization.
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct InitAccount {
pub max_players: u16,
pub data: Vec<u8>,
Expand Down
2 changes: 1 addition & 1 deletion api/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl Settle {
}
}

#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq)]
#[derive(Clone, BorshSerialize, BorshDeserialize, Debug, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Award {
Expand Down
91 changes: 80 additions & 11 deletions core/src/checkpoint.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use std::{collections::HashMap, fmt::Display};

use borsh::{BorshDeserialize, BorshSerialize};
use rs_merkle::{
algorithms::Sha256, proof_serializers::ReverseHashesOrder, Hasher,
MerkleTree,
use crate::{
context::{DispatchEvent, Versions},
error::Error,
};
use crate::{context::Versions, error::Error};
use borsh::{BorshDeserialize, BorshSerialize};
use rs_merkle::{algorithms::Sha256, proof_serializers::ReverseHashesOrder, Hasher, MerkleTree};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use race_api::types::GameId;
use crate::types::GameSpec;
use race_api::{effect::{EmitBridgeEvent, SubGame}, types::GameId};

/// Checkpoint represents the state snapshot of game.
/// It is used as a submission to the blockchain.
Expand All @@ -23,6 +23,7 @@ pub struct Checkpoint {
pub access_version: u64,
pub data: HashMap<GameId, VersionedData>,
pub proofs: HashMap<GameId, Vec<u8>>,
pub launch_subgames: Vec<SubGame>,
}

#[derive(Default, Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
Expand All @@ -40,6 +41,7 @@ pub struct CheckpointOnChain {
pub struct CheckpointOffChain {
pub data: HashMap<GameId, VersionedData>,
pub proofs: HashMap<GameId, Vec<u8>>,
pub launch_subgames: Vec<SubGame>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
Expand All @@ -50,6 +52,15 @@ pub struct VersionedData {
pub data: Vec<u8>,
pub sha: Vec<u8>,
pub game_spec: GameSpec,
pub dispatch: Option<DispatchEvent>,
pub bridge_events: Vec<EmitBridgeEvent>,
}

impl VersionedData {
fn clear_future_events(&mut self) {
self.dispatch = None;
self.bridge_events.clear();
}
}

impl Display for Checkpoint {
Expand Down Expand Up @@ -77,20 +88,27 @@ impl Checkpoint {
versions,
data: root_data,
sha: sha.into(),
dispatch: None,
bridge_events: vec![],
},
)]),
proofs: HashMap::new(),
launch_subgames: vec![],
};
ret.update_root_and_proofs();
ret
}

pub fn new_from_parts(offchain_part: CheckpointOffChain, onchain_part: CheckpointOnChain) -> Self {
pub fn new_from_parts(
offchain_part: CheckpointOffChain,
onchain_part: CheckpointOnChain,
) -> Self {
Self {
proofs: offchain_part.proofs,
data: offchain_part.data,
access_version: onchain_part.access_version,
root: onchain_part.root,
launch_subgames: offchain_part.launch_subgames,
}
}

Expand All @@ -103,7 +121,7 @@ impl Checkpoint {
return;
}
let merkle_tree = self.to_merkle_tree();
let Some(root) = merkle_tree.root() else {
let Some(root) = merkle_tree.root() else {
// Skip root update as this is not a master checkpoint
return;
};
Expand Down Expand Up @@ -142,7 +160,13 @@ impl Checkpoint {
}
}

pub fn init_data(&mut self, id: GameId, game_spec: GameSpec, versions: Versions, data: Vec<u8>) -> Result<(), Error> {
pub fn init_data(
&mut self,
id: GameId,
game_spec: GameSpec,
versions: Versions,
data: Vec<u8>,
) -> Result<(), Error> {
match self.data.entry(id) {
std::collections::hash_map::Entry::Occupied(_) => {
return Err(Error::CheckpointAlreadyExists);
Expand All @@ -156,6 +180,8 @@ impl Checkpoint {
sha: sha.into(),
game_spec,
versions,
dispatch: None,
bridge_events: vec![],
};
v.insert(versioned_data);
self.update_root_and_proofs();
Expand Down Expand Up @@ -188,6 +214,48 @@ impl Checkpoint {
}
}

pub fn set_dispatch_in_versioned_data(
&mut self,
id: GameId,
dispatch: Option<DispatchEvent>,
) -> Result<(), Error> {
if let Some(versioned_data) = self.data.get_mut(&id) {
versioned_data.dispatch = dispatch;
Ok(())
} else {
Err(Error::MissingCheckpoint)
}
}

pub fn set_bridge_in_versioned_data(
&mut self,
id: GameId,
bridge_events: Vec<EmitBridgeEvent>,
) -> Result<(), Error> {
if let Some(versioned_data) = self.data.get_mut(&id) {
versioned_data.bridge_events = bridge_events;
Ok(())
} else {
Err(Error::MissingCheckpoint)
}
}

pub fn append_launch_subgames(&mut self, subgame: SubGame) {
self.launch_subgames.push(subgame);
}

pub fn delete_launch_subgames(&mut self, id: GameId) {
self.launch_subgames.retain(|subgame| subgame.id != id);
}

pub fn get_launch_subgames(&self) -> Vec<SubGame> {
self.launch_subgames.clone()
}

pub fn clear_future_events(&mut self) {
self.data.values_mut().for_each(VersionedData::clear_future_events);
}

pub fn list_versioned_data(&self) -> Vec<&VersionedData> {
self.data.values().collect()
}
Expand All @@ -203,14 +271,14 @@ impl Checkpoint {
pub fn get_sha(&self, id: GameId) -> Option<[u8; 32]> {
self.data
.get(&id)
.map(|d| d.sha.clone().try_into().unwrap())
.map(|d| d.sha.clone().try_into().expect("Failed to get SHA"))
}

pub fn to_merkle_tree(&self) -> MerkleTree<Sha256> {
let mut leaves: Vec<[u8; 32]> = vec![];
let mut i = 0;
while let Some(vd) = self.data.get(&i) {
leaves.push(vd.sha.clone().try_into().unwrap());
leaves.push(vd.sha.clone().try_into().expect("Failed to build merkle tree"));
i += 1;
}
MerkleTree::from_leaves(&leaves)
Expand All @@ -232,6 +300,7 @@ impl Checkpoint {
CheckpointOffChain {
proofs: self.proofs.clone(),
data: self.data.clone(),
launch_subgames: self.launch_subgames.clone(),
}
}

Expand Down
Loading
Loading