Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 13 additions & 8 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ CHAIN="${INFO[0]}"
OWNER="${INFO[1]}"
```

### Creating the scoring chain
### Creating the scoring chains

Let's create another wallet and the scoring chain. The app wallet will be accessible with `linera -w1`.
Let's create another wallet and two scoring chains. The app wallet will be accessible with `linera -w1`.

```bash
export LINERA_WALLET_1="$LINERA_TMP_DIR/wallet_1.json"
Expand All @@ -57,6 +57,10 @@ linera -w1 wallet init --faucet $FAUCET_URL
INFO_1=($(linera -w1 wallet request-chain --faucet $FAUCET_URL))
CHAIN_1="${INFO_1[0]}"
OWNER_1="${INFO_1[1]}"

INFO_2=($(linera -w1 wallet request-chain --faucet $FAUCET_URL))
CHAIN_2="${INFO_1[0]}"
OWNER_2="${INFO_1[1]}"
```

### Creating the GoL challenge application
Expand All @@ -66,8 +70,7 @@ node service.

```bash
APP_ID=$(linera --wait-for-outgoing-messages \
project publish-and-create backend gol_challenge $CHAIN \
--json-parameters "{ \"scoring_chain_id\": \"$CHAIN_1\" }")
project publish-and-create backend gol_challenge $CHAIN)
```

### Creating a new puzzle
Expand All @@ -80,18 +83,18 @@ BLOB_ID=$(linera publish-data-blob "$LINERA_TMP_DIR/02_beehive_pattern_puzzle.bc

### Publishing puzzles and running code-generation

Run the node service for the scoring chain.
Run the node service for the scoring chains.
```bash
linera -w1 service --port 8081 &
sleep 1
```

The following script creates puzzles with the `gol` tool, then it uses the user wallet to
publish them. At the same time, it also sends GraphQL queries to the scoring chain to register
publish them. At the same time, it also sends GraphQL queries to the scoring chain(s) to register
the puzzles.

```bash
./publish-puzzles.sh http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID
./publish-puzzles.sh http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID http://localhost:8081/chains/$CHAIN_2/applications/$APP_ID
```

Note that we never unregister puzzles.
Expand Down Expand Up @@ -121,7 +124,7 @@ query {

```gql,uri=http://localhost:8080/chains/$CHAIN/applications/$APP_ID
mutation {
submitSolution(puzzleId: "$BLOB_ID", board: {
submitSolution(puzzleId: "$BLOB_ID", scoringChainId: "$CHAIN_1", board: {
size: 9,
liveCells: [{x: 3, y: 2}, {x: 4, y: 2}, {x: 2, y: 3}, {x: 5, y: 3}, {x: 3, y: 4}, {x: 4, y: 4}]
})
Expand All @@ -133,6 +136,7 @@ mutation {
To debug GraphQL APIs, uncomment the line with `read` and run `bash -x -e <(linera extract-script-from-markdown backend/README.md)`.
```bash
echo http://localhost:8081/chains/$CHAIN_1/applications/$APP_ID
echo http://localhost:8081/chains/$CHAIN_2/applications/$APP_ID
# read
```

Expand Down Expand Up @@ -161,6 +165,7 @@ Restart the service with the scoring chain followed in read-only:
kill $PID

linera wallet follow-chain "$CHAIN_1"
linera wallet follow-chain "$CHAIN_2"

linera service --port 8080 &
```
Expand Down
59 changes: 25 additions & 34 deletions backend/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod state;
use async_graphql::ComplexObject;
use gol_challenge::{game::Puzzle, GolChallengeAbi, Operation};
use linera_sdk::{
linera_base_types::{AccountOwner, ChainId, DataBlobHash, Timestamp, WithContractAbi},
linera_base_types::{AccountOwner, DataBlobHash, Timestamp, WithContractAbi},
views::{RootView, View},
Contract, ContractRuntime,
};
Expand All @@ -22,12 +22,6 @@ pub struct GolChallengeContract {

linera_sdk::contract!(GolChallengeContract);

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Parameters {
/// Where to report puzzles for scoring.
scoring_chain_id: ChainId,
}

impl WithContractAbi for GolChallengeContract {
type Abi = GolChallengeAbi;
}
Expand All @@ -45,7 +39,7 @@ pub struct Message {
impl Contract for GolChallengeContract {
type Message = Message;
type InstantiationArgument = ();
type Parameters = Parameters;
type Parameters = ();
type EventValue = ();

async fn load(runtime: ContractRuntime<Self>) -> Self {
Expand All @@ -68,6 +62,7 @@ impl Contract for GolChallengeContract {
puzzle_id,
board,
owner,
scoring_chain_id,
} => {
let owner = owner.unwrap_or_else(|| {
self.runtime
Expand All @@ -88,18 +83,19 @@ impl Contract for GolChallengeContract {
.insert(&puzzle_id, solution)
.expect("Store solution");

let Parameters { scoring_chain_id } = self.runtime.application_parameters();
let message = Message {
puzzle_id,
timestamp,
owner,
};
self.runtime
.prepare_message(message)
.send_to(scoring_chain_id);
if let Some(scoring_chain_id) = scoring_chain_id {
let message = Message {
puzzle_id,
timestamp,
owner,
};
self.runtime
.prepare_message(message)
.send_to(scoring_chain_id);
}
}
Operation::RegisterPuzzle { puzzle_id } => {
self.assert_scoring_chain("Puzzles are only registered on the scoring chain.");
// Puzzles are only registered on a scoring chain.
self.state.registered_puzzles.insert(&puzzle_id).unwrap();
}
}
Expand All @@ -112,35 +108,30 @@ impl Contract for GolChallengeContract {
timestamp,
owner,
} = message;
self.assert_scoring_chain("Messages are sent to the scoring chain.");
let is_registered = self
.state
.registered_puzzles
.contains(&puzzle_id)
.await
.unwrap();
assert!(is_registered, "Puzzle must be registered");
let map = self
.state
.reported_solutions
.load_entry_mut(&owner)
.await
.unwrap();
map.insert(&puzzle_id, timestamp).unwrap();
if is_registered {
let map = self
.state
.reported_solutions
.load_entry_mut(&owner)
.await
.unwrap();
map.insert(&puzzle_id, timestamp).unwrap();
} else {
log::trace!("Ignoring unregistered puzzle");
}
}

async fn store(mut self) {
self.state.save().await.expect("Failed to save state");
}
}

impl GolChallengeContract {
fn assert_scoring_chain(&mut self, error: &str) {
let Parameters { scoring_chain_id } = self.runtime.application_parameters();
assert_eq!(self.runtime.chain_id(), scoring_chain_id, "{}", error);
}
}

/// This implementation is only nonempty in the service.
#[ComplexObject]
impl GolChallengeState {}
4 changes: 3 additions & 1 deletion backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub mod game;
use async_graphql::{Request, Response};
use linera_sdk::{
graphql::GraphQLMutationRoot,
linera_base_types::{AccountOwner, ContractAbi, DataBlobHash, ServiceAbi},
linera_base_types::{AccountOwner, ChainId, ContractAbi, DataBlobHash, ServiceAbi},
};
use serde::{Deserialize, Serialize};

Expand All @@ -31,6 +31,8 @@ pub enum Operation {
board: Board,
/// Optional owner to credit instead of the current authenticated owner.
owner: Option<AccountOwner>,
/// The scoring chain to use.
scoring_chain_id: Option<ChainId>,
},
// Scoring appchain only
/// Register a puzzle to activate scoring for it.
Expand Down
3 changes: 2 additions & 1 deletion backend/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ use serde::{Deserialize, Serialize};
#[graphql(complex)]
#[view(context = ViewStorageContext)]
pub struct GolChallengeState {
// User chains only.
/// The local solutions previously submitted by an owner of the chain. Puzzles do not
/// need to be registered.
pub solutions: MapView<DataBlobHash, Solution>,

// Scoring chain only.
// Scoring chains only.
/// The set of registered puzzles.
pub registered_puzzles: SetView<DataBlobHash>,
/// The set of all solutions reported to us, indexed by owner, then by puzzle_id. We only track
Expand Down
10 changes: 6 additions & 4 deletions publish-puzzles.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

set -e

# Expecting some URI to query the node service of the scoring chain.
# Expecting some URIs to query the node service of the scoring chain.
# If absent, puzzles will be published but not registered for scoring.
URI="$1"
URIS=("$@")

echo "Using ${#URIS[@]} scoring chains"

# Create temporary directory
TEMP_DIR=$(mktemp -d)
Expand Down Expand Up @@ -63,12 +65,12 @@ for i in "${!PUZZLE_FILES[@]}"; do
echo " \"$PUZZLE\": \"$BLOB_ID\"," >> "$BLOB_MAP_FILE"
fi

if [ -n "$URI" ]; then
for URI in "${URIS[@]}"; do
QUERY="mutation { registerPuzzle(puzzleId: \"$BLOB_ID\")}"
JSON_QUERY=$( jq -n --arg q "$QUERY" '{"query": $q}' )
# Use js to parse the result and fail otherwise.
curl -w '\n' -g -X POST -H "Content-Type: application/json" -d "$JSON_QUERY" "$URI" | tee /dev/stderr | jq -e .data
fi
done
else
echo " Error: Could not extract blob ID for $PUZZLE"
exit 1
Expand Down