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
3 changes: 3 additions & 0 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,9 @@ Run a GraphQL service that exposes a faucet where users can claim tokens. This g
Default value: `8080`
* `--amount <AMOUNT>` — The number of tokens to send to each new chain
* `--limit-rate-until <LIMIT_RATE_UNTIL>` — The end timestamp: The faucet will rate-limit the token supply so it runs out of money no earlier than this
* `--max-chain-length <MAX_CHAIN_LENGTH>` — The maximum number of blocks in the faucet chain, before a new one is created

Default value: `100`
* `--listener-skip-process-inbox` — Do not create blocks automatically to receive incoming messages. Instead, wait for an explicit mutation `processInbox`
* `--listener-delay-before-ms <DELAY_BEFORE_MS>` — Wait before processing any notification (useful for testing)

Expand Down
88 changes: 68 additions & 20 deletions linera-faucet/server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use axum::{Extension, Router};
use futures::{lock::Mutex, FutureExt as _};
use linera_base::{
crypto::{CryptoHash, ValidatorPublicKey},
data_types::{Amount, ApplicationPermissions, Timestamp},
data_types::{Amount, ApplicationPermissions, BlockHeight, Timestamp},
identifiers::{AccountOwner, ChainId, MessageId},
ownership::ChainOwnership,
};
Expand Down Expand Up @@ -43,14 +43,15 @@ mod tests;
pub struct QueryRoot<C> {
context: Arc<Mutex<C>>,
genesis_config: Arc<GenesisConfig>,
chain_id: ChainId,
chain_id: Arc<Mutex<ChainId>>,
}

/// The root GraphQL mutation type.
pub struct MutationRoot<C> {
chain_id: ChainId,
chain_id: Arc<Mutex<ChainId>>,
context: Arc<Mutex<C>>,
amount: Amount,
end_block_height: BlockHeight,
end_timestamp: Timestamp,
start_timestamp: Timestamp,
start_balance: Amount,
Expand Down Expand Up @@ -90,7 +91,8 @@ where

/// Returns the current committee's validators.
async fn current_validators(&self) -> Result<Vec<Validator>, Error> {
let client = self.context.lock().await.make_chain_client(self.chain_id)?;
let chain_id = *self.chain_id.lock().await;
let client = self.context.lock().await.make_chain_client(chain_id)?;
let committee = client.local_committee().await?;
Ok(committee
.validators()
Expand Down Expand Up @@ -119,7 +121,8 @@ where
C: ClientContext,
{
async fn do_claim(&self, owner: AccountOwner) -> Result<ClaimOutcome, Error> {
let client = self.context.lock().await.make_chain_client(self.chain_id)?;
let chain_id = *self.chain_id.lock().await;
let client = self.context.lock().await.make_chain_client(chain_id)?;

if self.start_timestamp < self.end_timestamp {
let local_time = client.storage_client().clock().current_time();
Expand Down Expand Up @@ -150,16 +153,32 @@ where
.open_chain(ownership, ApplicationPermissions::default(), self.amount)
.await;
self.context.lock().await.update_wallet(&client).await?;
let (message_id, certificate) = match result? {
ClientOutcome::Committed(result) => result,
ClientOutcome::WaitForTimeout(timeout) => {
return Err(Error::new(format!(
"This faucet is using a multi-owner chain and is not the leader right now. \
Try again at {}",
timeout.timestamp,
)));
}
};
let (message_id, certificate) = result?.try_unwrap()?;

if client.next_block_height() >= self.end_block_height {
let key_pair = client.key_pair().await?;
let balance = client.local_balance().await?.try_sub(Amount::ONE)?;
let ownership = client.chain_state_view().await?.ownership().clone();
let (message_id, certificate) = client
.open_chain(ownership, ApplicationPermissions::default(), balance)
.await?
.try_unwrap()?;
// TODO(#1795): Move the remaining tokens to the new chain.
client.close_chain().await?.try_unwrap()?;
let chain_id = ChainId::child(message_id);
info!("Switching to a new faucet chain {chain_id:8}; remaining balance: {balance}");
self.context
.lock()
.await
.update_wallet_for_new_chain(
chain_id,
Some(key_pair),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the same keypair on child chains?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was easiest to do it that way, and maybe it's also better: That way you don't have to keep backing up new key pairs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will change in a moment with the new Signer abstraction anyway.

certificate.block().header.timestamp,
)
.await?;
*self.chain_id.lock().await = chain_id;
}

let chain_id = ChainId::child(message_id);
Ok(ClaimOutcome {
message_id,
Expand All @@ -186,14 +205,15 @@ pub struct FaucetService<C>
where
C: ClientContext,
{
chain_id: ChainId,
chain_id: Arc<Mutex<ChainId>>,
context: Arc<Mutex<C>>,
genesis_config: Arc<GenesisConfig>,
config: ChainListenerConfig,
storage: <C::Environment as linera_core::Environment>::Storage,
port: NonZeroU16,
amount: Amount,
end_timestamp: Timestamp,
end_block_height: BlockHeight,
start_timestamp: Timestamp,
start_balance: Amount,
}
Expand All @@ -204,13 +224,14 @@ where
{
fn clone(&self) -> Self {
Self {
chain_id: self.chain_id,
chain_id: self.chain_id.clone(),
context: Arc::clone(&self.context),
genesis_config: Arc::clone(&self.genesis_config),
config: self.config.clone(),
storage: self.storage.clone(),
port: self.port,
amount: self.amount,
end_block_height: self.end_block_height,
end_timestamp: self.end_timestamp,
start_timestamp: self.start_timestamp,
start_balance: self.start_balance,
Expand All @@ -229,6 +250,7 @@ where
chain_id: ChainId,
context: C,
amount: Amount,
end_block_height: BlockHeight,
end_timestamp: Timestamp,
genesis_config: Arc<GenesisConfig>,
config: ChainListenerConfig,
Expand All @@ -240,13 +262,14 @@ where
client.process_inbox().await?;
let start_balance = client.local_balance().await?;
Ok(Self {
chain_id,
chain_id: Arc::new(Mutex::new(chain_id)),
context,
genesis_config,
config,
storage,
port,
amount,
end_block_height,
end_timestamp,
start_timestamp,
start_balance,
Expand All @@ -255,17 +278,18 @@ where

pub fn schema(&self) -> Schema<QueryRoot<C>, MutationRoot<C>, EmptySubscription> {
let mutation_root = MutationRoot {
chain_id: self.chain_id,
chain_id: self.chain_id.clone(),
context: Arc::clone(&self.context),
amount: self.amount,
end_block_height: self.end_block_height,
end_timestamp: self.end_timestamp,
start_timestamp: self.start_timestamp,
start_balance: self.start_balance,
};
let query_root = QueryRoot {
genesis_config: Arc::clone(&self.genesis_config),
context: Arc::clone(&self.context),
chain_id: self.chain_id,
chain_id: self.chain_id.clone(),
};
Schema::build(query_root, mutation_root, EmptySubscription).finish()
}
Expand Down Expand Up @@ -304,3 +328,27 @@ where
schema.execute(request.into_inner()).await.into()
}
}

trait ClientOutcomeExt {
type Output;

/// Returns the committed result or an error if we are not the leader.
///
/// It is recommended to use single-owner chains for the faucet to avoid this error.
fn try_unwrap(self) -> Result<Self::Output, Error>;
}

impl<T> ClientOutcomeExt for ClientOutcome<T> {
type Output = T;

fn try_unwrap(self) -> Result<Self::Output, Error> {
match self {
ClientOutcome::Committed(result) => Ok(result),
ClientOutcome::WaitForTimeout(timeout) => Err(Error::new(format!(
"This faucet is using a multi-owner chain and is not the leader right now. \
Try again at {}",
timeout.timestamp,
))),
}
}
}
5 changes: 3 additions & 2 deletions linera-faucet/server/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use async_trait::async_trait;
use futures::lock::Mutex;
use linera_base::{
crypto::{AccountPublicKey, AccountSecretKey},
data_types::{Amount, Timestamp},
data_types::{Amount, BlockHeight, Timestamp},
identifiers::ChainId,
};
use linera_client::{chain_listener, wallet::Wallet};
Expand Down Expand Up @@ -82,9 +82,10 @@ async fn test_faucet_rate_limiting() {
};
let context = Arc::new(Mutex::new(context));
let root = MutationRoot {
chain_id,
chain_id: Arc::new(Mutex::new(chain_id)),
context: context.clone(),
amount: Amount::from_tokens(1),
end_block_height: BlockHeight::from(10),
end_timestamp: Timestamp::from(6000),
start_timestamp: Timestamp::from(0),
start_balance: Amount::from_tokens(6),
Expand Down
13 changes: 10 additions & 3 deletions linera-service/src/cli_wrappers/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,15 +519,22 @@ impl ClientWrapper {
port: impl Into<Option<u16>>,
chain_id: ChainId,
amount: Amount,
max_chain_length: Option<u64>,
) -> Result<FaucetService> {
let port = port.into().unwrap_or(8080);
let mut command = self.command().await?;
let child = command
command
.arg("faucet")
.arg(chain_id.to_string())
.args(["--port".to_string(), port.to_string()])
.args(["--amount".to_string(), amount.to_string()])
.spawn_into()?;
.args(["--amount".to_string(), amount.to_string()]);
if let Some(max_chain_length) = max_chain_length {
command.args([
"--max-chain-length".to_string(),
max_chain_length.to_string(),
]);
}
let child = command.spawn_into()?;
let client = reqwest_client();
for i in 0..10 {
linera_base::time::timer::sleep(Duration::from_secs(i)).await;
Expand Down
4 changes: 4 additions & 0 deletions linera-service/src/linera/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,10 @@ pub enum ClientCommand {
#[arg(long)]
limit_rate_until: Option<DateTime<Utc>>,

/// The maximum number of blocks in the faucet chain, before a new one is created.
#[arg(long, default_value = "100")]
max_chain_length: u64,

/// Configuration for the faucet chain listener.
#[command(flatten)]
config: ChainListenerConfig,
Expand Down
4 changes: 3 additions & 1 deletion linera-service/src/linera/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use command::{ClientCommand, DatabaseToolCommand, NetCommand, ProjectCommand, Wa
use futures::{lock::Mutex, FutureExt as _, StreamExt};
use linera_base::{
crypto::{AccountSecretKey, CryptoHash, CryptoRng, Ed25519SecretKey},
data_types::{ApplicationPermissions, Timestamp},
data_types::{ApplicationPermissions, BlockHeight, Timestamp},
identifiers::{AccountOwner, ChainDescription, ChainId},
listen_for_shutdown_signals,
ownership::ChainOwnership,
Expand Down Expand Up @@ -834,6 +834,7 @@ impl Runnable for Job {
port,
amount,
limit_rate_until,
max_chain_length,
config,
} => {
let chain_id = chain_id.unwrap_or_else(|| context.default_chain());
Expand All @@ -851,6 +852,7 @@ impl Runnable for Job {
chain_id,
context,
amount,
BlockHeight(max_chain_length),
end_timestamp,
genesis_config,
config,
Expand Down
2 changes: 1 addition & 1 deletion linera-service/src/linera/net_up_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ async fn print_messages_and_create_faucet(
ChainId::root(1)
};
let service = client
.run_faucet(Some(faucet_port.into()), faucet_chain, faucet_amount)
.run_faucet(Some(faucet_port.into()), faucet_chain, faucet_amount, None)
.await?;
Some(service)
} else {
Expand Down
19 changes: 16 additions & 3 deletions linera-service/tests/linera_net_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3166,7 +3166,7 @@ async fn test_end_to_end_faucet(config: impl LineraNetConfig) -> Result<()> {
let owner2 = client2.keygen().await?;

let mut faucet_service = client1
.run_faucet(None, chain1, Amount::from_tokens(2))
.run_faucet(None, chain1, Amount::from_tokens(2), None)
.await?;
let faucet = faucet_service.instance();
let outcome = faucet.claim(&owner2).await?;
Expand Down Expand Up @@ -3259,16 +3259,29 @@ async fn test_end_to_end_faucet_with_long_chains(config: impl LineraNetConfig) -
}

let amount = Amount::ONE;
let mut faucet_service = faucet_client.run_faucet(None, faucet_chain, amount).await?;
let mut faucet_service = faucet_client
.run_faucet(None, faucet_chain, amount, Some(chain_count as u64))
.await?;
let faucet = faucet_service.instance();

// Create a new wallet using the faucet
let other_client = net.make_client().await;
let (other_outcome, _) = other_client
.wallet_init(&[], FaucetOption::NewChain(&faucet))
.await?
.unwrap();

// Create a new wallet using the faucet
let client = net.make_client().await;
let (outcome, _) = client
.wallet_init(&[], FaucetOption::NewChain(&faucet))
.await?
.unwrap();

// Since the faucet chain exceeds the configured maximum length, the faucet should have
// switched after the first new chain.
assert!(other_outcome.message_id.chain_id != outcome.message_id.chain_id);

let chain = outcome.chain_id;
assert_eq!(chain, client.load_wallet()?.default_chain().unwrap());

Expand Down Expand Up @@ -3311,7 +3324,7 @@ async fn test_end_to_end_fungible_client_benchmark(config: impl LineraNetConfig)

let chain1 = client1.load_wallet()?.default_chain().unwrap();

let mut faucet_service = client1.run_faucet(None, chain1, Amount::ONE).await?;
let mut faucet_service = client1.run_faucet(None, chain1, Amount::ONE, None).await?;
let faucet = faucet_service.instance();

let path =
Expand Down
Loading