Skip to content

Commit

Permalink
Verify chat interactions on the central chain (#4)
Browse files Browse the repository at this point in the history
* Ask creation chain to verify chat interactions

When a chat interaction is requested to be logged, send a message to the
creation chain to check if the interaction was signed by an Atoma node.
If the check succeeds, send back a message authorizing the interaction
to be logged.

* Update chat interacting logging unit test

Test if the authorization message leads to the chat being logged,
instead of the request operation for logging the chat.

* Test for request to verify signature

Check that an operation requesting a chat interaction to be logged leads
to a message requesting the interaction to be verified.

* Test full chat interaction verification flow

Ensure that the messages are correctly sent between chains and that it
leads to the chat interaction being logged on the chain.
  • Loading branch information
jvff authored Feb 15, 2025
1 parent 4a25811 commit 8087998
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 10 deletions.
53 changes: 49 additions & 4 deletions src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use linera_sdk::{
views::{RootView, View},
Contract, ContractRuntime,
};
use serde::{Deserialize, Serialize};

use self::state::Application;

Expand All @@ -29,7 +30,7 @@ impl WithContractAbi for ApplicationContract {
}

impl Contract for ApplicationContract {
type Message = ();
type Message = Message;
type Parameters = ();
type InstantiationArgument = ();

Expand All @@ -49,13 +50,30 @@ impl Contract for ApplicationContract {
}
}

async fn execute_message(&mut self, _message: Self::Message) {}
async fn execute_message(&mut self, message: Self::Message) {
match message {
Message::VerifySignature(interaction) => self.verify_signature(interaction),
Message::LogVerifiedChatInteraction(interaction) => {
self.log_verified_chat_interaction(interaction)
}
}
}

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

/// Cross-chain messages sent privately between the application shards.
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub enum Message {
/// Request to verify a [`ChatInteraction`]'s signature.
VerifySignature(ChatInteraction),

/// Response indicating that the [`ChatInteraction`]'s signature was verified and approved.
LogVerifiedChatInteraction(ChatInteraction),
}

impl ApplicationContract {
/// Handles an [`Operation::UpdateNodes`] by adding the `nodes_to_add` and removing the
/// `nodes_to_remove`.
Expand Down Expand Up @@ -98,9 +116,36 @@ impl ApplicationContract {
);
}

/// Handles an [`Operation::LogChatInteraction`] by adding a [`ChatInteraction`] to the chat
/// log.
/// Handles an [`Operation::LogChatInteraction`] by requesting the [`ChatInteraction`]'s
/// signature to be verified.
fn log_chat_interaction(&mut self, interaction: ChatInteraction) {
let creation_chain_id = self.runtime.application_id().creation.chain_id;

self.runtime
.send_message(creation_chain_id, Message::VerifySignature(interaction));
}

/// Handles a [`Message::VerifySignature`] by verifying the signature and if accepted,
/// responding with a [`Message::LogVerifiedChatInteraction`].
fn verify_signature(&mut self, interaction: ChatInteraction) {
let requester_chain_id = self
.runtime
.message_id()
.expect(
"`verify_signature` should only be called \
when handling a `Message::VerifySignature`",
)
.chain_id;

self.runtime.send_message(
requester_chain_id,
Message::LogVerifiedChatInteraction(interaction),
);
}

/// Handles a [`Message::LogVerifiedChatInteraction`] by adding the [`ChatInteraction`] to the
/// chat log.
fn log_verified_chat_interaction(&mut self, interaction: ChatInteraction) {
self.state.chat_log.push(interaction);
}
}
41 changes: 36 additions & 5 deletions src/contract_unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use std::{

use atoma_demo::{ChatInteraction, Operation, PublicKey};
use linera_sdk::{
base::{ApplicationId, ChainId},
base::{ApplicationId, ChainId, Destination},
util::BlockingWait,
Contract, ContractRuntime,
Contract, ContractRuntime, Resources, SendMessageRequest,
};
use proptest::{
prelude::{Arbitrary, BoxedStrategy},
Expand All @@ -20,7 +20,7 @@ use proptest::{
use rand::Rng;
use test_strategy::proptest;

use super::ApplicationContract;
use super::{ApplicationContract, Message};

/// Tests if nodes can be added to and removed from the set of active Atoma nodes.
#[proptest]
Expand Down Expand Up @@ -96,14 +96,45 @@ fn cant_add_and_remove_node_in_the_same_operation(
assert!(result.is_err());
}

/// Tests if chat interactions are requested to be verified.
#[proptest]
fn chat_interaction_is_requested_to_be_verified(
application_id: ApplicationId<atoma_demo::ApplicationAbi>,
interaction: ChatInteraction,
) {
let mut contract = setup_contract();

contract.runtime.set_application_id(application_id);

contract
.execute_operation(Operation::LogChatInteraction {
interaction: interaction.clone(),
})
.blocking_wait();

let messages = contract.runtime.created_send_message_requests();

assert_eq!(messages.len(), 1);
assert_eq!(
messages[0],
SendMessageRequest {
destination: Destination::Recipient(application_id.creation.chain_id),
authenticated: false,
is_tracked: false,
grant: Resources::default(),
message: Message::VerifySignature(interaction),
}
);
}

/// Tests if chat interactions are logged on chain.
#[proptest]
fn chat_interactions_are_logged_on_chain(interactions: Vec<ChatInteraction>) {
fn verified_chat_interactions_are_logged_on_chain(interactions: Vec<ChatInteraction>) {
let mut contract = setup_contract();

for interaction in interactions.clone() {
contract
.execute_operation(Operation::LogChatInteraction { interaction })
.execute_message(Message::LogVerifiedChatInteraction(interaction))
.blocking_wait();
}

Expand Down
79 changes: 78 additions & 1 deletion tests/chat_transcript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use std::env;

use atoma_demo::{ApplicationAbi, ChatInteraction, Operation};
use atoma_demo::{ApplicationAbi, ChatInteraction, Operation, PublicKey};
use linera_sdk::{bcs, test::TestValidator};

/// Tests if the service queries the Atoma network when handling a `chat` mutation.
Expand Down Expand Up @@ -64,3 +64,80 @@ async fn service_queries_atoma() {

assert!(response.contains("Rio de Janeiro"));
}

/// Tests if a chat interaction is verified on the creation chain and logged on the requesting
/// chain.
#[test_log::test(tokio::test)]
async fn chat_interaction_verification_and_logging() {
let (validator, application_id, creation_chain) =
TestValidator::with_current_application::<ApplicationAbi, _, _>((), ()).await;

let fake_node = PublicKey::from([0_u8; 32]);
let chat_prompt = "What is one plus one?";
let chat_response = "2";

creation_chain
.add_block(|block| {
block.with_operation(
application_id,
Operation::UpdateNodes {
add: vec![fake_node],
remove: vec![],
},
);
})
.await;

let chat_chain = validator.new_chain().await;

chat_chain.register_application(application_id).await;

let request_certificate = chat_chain
.add_block(|block| {
block.with_operation(
application_id,
Operation::LogChatInteraction {
interaction: ChatInteraction {
prompt: chat_prompt.to_owned(),
response: chat_response.to_owned(),
},
},
);
})
.await;

let verification_certificate = creation_chain
.add_block(|block| {
block.with_messages_from(&request_certificate);
})
.await;

chat_chain
.add_block(|block| {
block.with_messages_from(&verification_certificate);
})
.await;

let response = chat_chain
.graphql_query(
application_id,
"query { chatLog { entries { prompt, response } } }",
)
.await;

assert_eq!(
response.to_string(),
format!(
"{{\
\"chatLog\":{{\
\"entries\":[\
{{\
\"prompt\":{chat_prompt:?},\
\"response\":{chat_response:?}\
}}\
]\
}}\
}}"
)
);
}

0 comments on commit 8087998

Please sign in to comment.