added admin functionality #7
16
snp.proto
16
snp.proto
@ -191,8 +191,18 @@ message ExtendVmReq {
|
||||
uint64 locked_nano = 3;
|
||||
}
|
||||
|
||||
message AirdropReq {
|
||||
string pubkey = 1;
|
||||
uint64 tokens = 2;
|
||||
}
|
||||
|
||||
message Account {
|
||||
string pubkey = 1;
|
||||
uint64 balance = 2;
|
||||
uint64 tmp_locked = 3;
|
||||
}
|
||||
|
||||
service BrainCli {
|
||||
rpc GetAirdrop (Pubkey) returns (Empty);
|
||||
rpc GetBalance (Pubkey) returns (AccountBalance);
|
||||
rpc NewVm (NewVmReq) returns (NewVmResp);
|
||||
rpc ListContracts (ListContractsReq) returns (stream Contract);
|
||||
@ -201,4 +211,8 @@ service BrainCli {
|
||||
rpc DeleteVm (DeleteVmReq) returns (Empty);
|
||||
rpc UpdateVm (UpdateVmReq) returns (UpdateVmResp);
|
||||
rpc ExtendVm (ExtendVmReq) returns (Empty);
|
||||
// admin commands
|
||||
rpc Airdrop (AirdropReq) returns (Empty);
|
||||
rpc ListAllContracts (Empty) returns (stream Contract);
|
||||
rpc ListAccounts (Empty) returns (stream Account);
|
||||
ghe0 marked this conversation as resolved
Outdated
|
||||
}
|
||||
|
20
src/data.rs
20
src/data.rs
@ -176,8 +176,8 @@ impl BrainData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_airdrop(&self, account: &str) {
|
||||
self.add_nano_to_wallet(account, 1000_000000000);
|
||||
pub fn give_airdrop(&self, account: &str, tokens: u64) {
|
||||
self.add_nano_to_wallet(account, tokens.saturating_mul(1_000_000_000));
|
||||
}
|
||||
|
||||
fn add_nano_to_wallet(&self, account: &str, nano_lp: u64) {
|
||||
@ -684,6 +684,22 @@ impl BrainData {
|
||||
contracts.iter().cloned().find(|c| c.uuid == uuid)
|
||||
}
|
||||
|
||||
pub fn list_all_contracts(&self) -> Vec<Contract> {
|
||||
let contracts = self.contracts.read().unwrap();
|
||||
contracts.iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn list_accounts(&self) -> Vec<grpc::Account> {
|
||||
self.accounts
|
||||
.iter()
|
||||
.map(|a| grpc::Account {
|
||||
pubkey: a.key().to_string(),
|
||||
balance: a.balance,
|
||||
tmp_locked: a.tmp_locked,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn find_contracts_by_admin_pubkey(&self, admin_pubkey: &str) -> Vec<Contract> {
|
||||
debug!("Searching contracts for admin pubkey {admin_pubkey}");
|
||||
let contracts: Vec<Contract> = self
|
||||
|
96
src/grpc.rs
96
src/grpc.rs
@ -15,6 +15,11 @@ use tokio::sync::mpsc;
|
||||
use tokio_stream::{wrappers::ReceiverStream, Stream, StreamExt};
|
||||
use tonic::{Request, Response, Status, Streaming};
|
||||
|
||||
const ADMIN_ACCOUNTS: &[&str] = &[
|
||||
"x52w7jARC5erhWWK65VZmjdGXzBK6ZDgfv1A283d8XK",
|
||||
"FHuecMbeC1PfjkW2JKyoicJAuiU7khgQT16QUB3Q1XdL",
|
||||
ghe0 marked this conversation as resolved
Outdated
valy
commented
not sure this wallet will actually be able to sign stuff 😆 not sure this wallet will actually be able to sign stuff 😆
ghe0
commented
Yes. I will need your address instead, so that you can give airdrops. Yes. I will need your address instead, so that you can give airdrops.
|
||||
];
|
||||
|
||||
pub struct BrainDaemonMock {
|
||||
data: Arc<BrainData>,
|
||||
}
|
||||
@ -99,7 +104,10 @@ impl BrainDaemon for BrainDaemonMock {
|
||||
let mut req_stream = req.into_inner();
|
||||
let pubkey: String;
|
||||
if let Some(Ok(msg)) = req_stream.next().await {
|
||||
log::debug!("demon_messages received the following auth message: {:?}", msg.msg);
|
||||
log::debug!(
|
||||
"demon_messages received the following auth message: {:?}",
|
||||
msg.msg
|
||||
);
|
||||
if let Some(daemon_message::Msg::Auth(auth)) = msg.msg {
|
||||
pubkey = auth.pubkey.clone();
|
||||
check_sig_from_parts(
|
||||
@ -149,12 +157,6 @@ impl BrainCli for BrainCliMock {
|
||||
Ok(Response::new(self.data.get_balance(&req.pubkey).into()))
|
||||
}
|
||||
|
||||
async fn get_airdrop(&self, req: Request<Pubkey>) -> Result<Response<Empty>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
self.data.get_airdrop(&req.pubkey);
|
||||
Ok(Response::new(Empty {}))
|
||||
}
|
||||
|
||||
async fn new_vm(&self, req: Request<NewVmReq>) -> Result<Response<NewVmResp>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
info!("New VM requested via CLI: {req:?}");
|
||||
@ -270,6 +272,54 @@ impl BrainCli for BrainCliMock {
|
||||
Err(e) => Err(Status::not_found(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn airdrop(&self, req: Request<AirdropReq>) -> Result<Response<Empty>, Status> {
|
||||
check_admin_key(&req)?;
|
||||
let req = check_sig_from_req(req)?;
|
||||
self.data.give_airdrop(&req.pubkey, req.tokens);
|
||||
Ok(Response::new(Empty{}))
|
||||
}
|
||||
|
||||
type ListAllContractsStream = Pin<Box<dyn Stream<Item = Result<Contract, Status>> + Send>>;
|
||||
async fn list_all_contracts(
|
||||
&self,
|
||||
req: Request<Empty>,
|
||||
) -> Result<Response<Self::ListContractsStream>, Status> {
|
||||
check_admin_key(&req)?;
|
||||
let _ = check_sig_from_req(req)?;
|
||||
let contracts = self.data.list_all_contracts();
|
||||
let (tx, rx) = mpsc::channel(6);
|
||||
tokio::spawn(async move {
|
||||
for contract in contracts {
|
||||
let _ = tx.send(Ok(contract.into())).await;
|
||||
}
|
||||
});
|
||||
let output_stream = ReceiverStream::new(rx);
|
||||
Ok(Response::new(
|
||||
Box::pin(output_stream) as Self::ListContractsStream
|
||||
))
|
||||
}
|
||||
|
||||
type ListAccountsStream = Pin<Box<dyn Stream<Item = Result<Account, Status>> + Send>>;
|
||||
async fn list_accounts(
|
||||
&self,
|
||||
req: Request<Empty>,
|
||||
) -> Result<Response<Self::ListAccountsStream>, Status> {
|
||||
check_admin_key(&req)?;
|
||||
let _ = check_sig_from_req(req)?;
|
||||
let accounts = self.data.list_accounts();
|
||||
ghe0 marked this conversation as resolved
Outdated
valy
commented
did you consider to clone the data arc instead and pass it to the thread? did you consider to clone the data arc instead and pass it to the thread?
ghe0
commented
I don't know what you mean. I don't know what you mean.
valy
commented
I think this operation will clone a full list of accounts, so I was thinking to just clone arc if the map of accounts is multithreaded I think this operation will clone a full list of accounts, so I was thinking to just clone arc if the map of accounts is multithreaded
ghe0
commented
Very good point, thank you! It's the same case for the contracts. I believe I can just pass the channel instead, so that each contract/account gets clone individually, instead of cloning the full list at once. Will submit a commit later today. Very good point, thank you! It's the same case for the contracts. I believe I can just pass the channel instead, so that each contract/account gets clone individually, instead of cloning the full list at once. Will submit a commit later today.
|
||||
let (tx, rx) = mpsc::channel(6);
|
||||
tokio::spawn(async move {
|
||||
for account in accounts {
|
||||
let _ = tx.send(Ok(account.into())).await;
|
||||
}
|
||||
});
|
||||
let output_stream = ReceiverStream::new(rx);
|
||||
Ok(Response::new(
|
||||
Box::pin(output_stream) as Self::ListAccountsStream
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trait PubkeyGetter {
|
||||
@ -324,6 +374,18 @@ impl PubkeyGetter for RegisterNodeReq {
|
||||
}
|
||||
}
|
||||
|
||||
impl PubkeyGetter for Empty {
|
||||
fn get_pubkey(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl PubkeyGetter for AirdropReq {
|
||||
fn get_pubkey(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> Result<T, Status> {
|
||||
let time = match req.metadata().get("timestamp") {
|
||||
Some(t) => t.clone(),
|
||||
@ -360,7 +422,7 @@ fn check_sig_from_req<T: std::fmt::Debug + PubkeyGetter>(req: Request<T>) -> Res
|
||||
|
||||
let pubkey_value = match req.metadata().get("pubkey") {
|
||||
Some(p) => p.clone(),
|
||||
None => return Err(Status::unauthenticated("Signature not found in metadata.")),
|
||||
None => return Err(Status::unauthenticated("pubkey not found in metadata.")),
|
||||
};
|
||||
let pubkey = ed25519_dalek::VerifyingKey::from_bytes(
|
||||
&bs58::decode(&pubkey_value)
|
||||
@ -426,3 +488,21 @@ fn check_sig_from_parts(pubkey: &str, time: &str, msg: &str, sig: &str) -> Resul
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_admin_key<T>(req: &Request<T>) -> Result<(), Status> {
|
||||
let pubkey = match req.metadata().get("pubkey") {
|
||||
Some(p) => p.clone(),
|
||||
None => return Err(Status::unauthenticated("pubkey not found in metadata.")),
|
||||
};
|
||||
let pubkey = pubkey
|
||||
.to_str()
|
||||
.map_err(|_| Status::unauthenticated("could not parse pubkey metadata to str"))?;
|
||||
|
||||
if !ADMIN_ACCOUNTS.contains(&pubkey) {
|
||||
return Err(Status::unauthenticated(
|
||||
"This operation is reserved to admin accounts",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user
maybe we can call this message similarly to the previous,
returns (stream Contract);
So instead of calling it
ListAccountsResp
, maybe call itAccount
?yeah, it that would work