solana/bridge: remove agent
Change-Id: I8fdf4e53172a422ee5d9d4dee13faf32ced1be08
This commit is contained in:
parent
723cf5fe95
commit
b02d782f1c
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = ["agent", "program", "client", "program_stub", "cpi_poster"]
|
||||
members = ["program", "client", "program_stub", "cpi_poster"]
|
||||
|
||||
[patch.crates-io]
|
||||
memmap2 = { path = "memmap2-rs" }
|
||||
memmap2 = { path = "memmap2-rs" }
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
target
|
File diff suppressed because it is too large
Load Diff
|
@ -1,37 +0,0 @@
|
|||
[package]
|
||||
name = "agent"
|
||||
version = "0.1.0"
|
||||
authors = ["Hendrik Hofstadt <hendrik@nexantic.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
tonic = "0.3.0"
|
||||
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
|
||||
prost = "0.6"
|
||||
prost-types = "0.6"
|
||||
solana-client = "=1.7.0"
|
||||
solana-program = "=1.7.0"
|
||||
solana-sdk = "=1.7.0"
|
||||
solitaire = { path = "../../solitaire/program" }
|
||||
solitaire-client = {path = "../../solitaire/client" }
|
||||
bridge = { path = "../program", features = ["no-entrypoint", "client"] }
|
||||
primitive-types = { version = "0.7.2" }
|
||||
hex = "0.4.2"
|
||||
thiserror = "1.0.20"
|
||||
tungstenite = "0.11.1"
|
||||
serde = "1.0.103"
|
||||
url = "2.1.1"
|
||||
serde_bytes = "0.11.5"
|
||||
log = "0.4.11"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.57"
|
||||
bs58 = "0.3.1"
|
||||
byteorder = "1.4.3"
|
||||
futures = "0.3.8"
|
||||
libc = "0.2.80"
|
||||
clap = "2.33.3"
|
||||
borsh = "0.8.1"
|
||||
sha3 = "0.9.1"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = { version = "0.3.0", features = ["prost"] }
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
tonic_build::compile_protos("../../../proto/agent/v1/service.proto").unwrap();
|
||||
}
|
|
@ -1,418 +0,0 @@
|
|||
use libc;
|
||||
use std::{
|
||||
fs,
|
||||
io::Write,
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use clap::{
|
||||
App,
|
||||
Arg,
|
||||
};
|
||||
|
||||
use byteorder::{
|
||||
LittleEndian,
|
||||
WriteBytesExt,
|
||||
};
|
||||
use futures::stream::TryStreamExt;
|
||||
use solana_client::{
|
||||
client_error::ClientError,
|
||||
rpc_client::RpcClient,
|
||||
rpc_config::RpcSendTransactionConfig,
|
||||
};
|
||||
use solana_sdk::{
|
||||
commitment_config::{
|
||||
CommitmentConfig,
|
||||
CommitmentLevel,
|
||||
},
|
||||
instruction::Instruction,
|
||||
pubkey::Pubkey,
|
||||
signature::{
|
||||
read_keypair_file,
|
||||
Keypair,
|
||||
Signature,
|
||||
Signer,
|
||||
},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use tokio::net::UnixListener;
|
||||
|
||||
use tonic::{
|
||||
transport::Server,
|
||||
Code,
|
||||
Request,
|
||||
Response,
|
||||
Status,
|
||||
};
|
||||
|
||||
use borsh::BorshDeserialize;
|
||||
use bridge::{
|
||||
accounts::{
|
||||
GuardianSet,
|
||||
GuardianSetDerivationData,
|
||||
},
|
||||
instructions::{
|
||||
hash_vaa,
|
||||
post_vaa,
|
||||
serialize_vaa,
|
||||
verify_signatures,
|
||||
},
|
||||
types::GuardianSetData,
|
||||
PostVAAData,
|
||||
VerifySignaturesData,
|
||||
};
|
||||
use service::{
|
||||
agent_server::{
|
||||
Agent,
|
||||
AgentServer,
|
||||
},
|
||||
GetBalanceRequest,
|
||||
GetBalanceResponse,
|
||||
SubmitVaaRequest,
|
||||
SubmitVaaResponse,
|
||||
};
|
||||
use sha3::Digest;
|
||||
use solitaire::{
|
||||
processors::seeded::Seeded,
|
||||
AccountState,
|
||||
};
|
||||
|
||||
mod socket;
|
||||
|
||||
pub mod service {
|
||||
include!(concat!(env!("OUT_DIR"), concat!("/", "agent.v1", ".rs")));
|
||||
}
|
||||
|
||||
pub struct AgentImpl {
|
||||
bridge: Pubkey,
|
||||
|
||||
rpc_url: String,
|
||||
key: Keypair,
|
||||
}
|
||||
|
||||
pub struct SignatureItem {
|
||||
signature: Vec<u8>,
|
||||
key: [u8; 20],
|
||||
index: u8,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Agent for AgentImpl {
|
||||
async fn submit_vaa(
|
||||
&self,
|
||||
request: Request<SubmitVaaRequest>,
|
||||
) -> Result<Response<SubmitVaaResponse>, Status> {
|
||||
// Hack to clone keypair
|
||||
let b = self.key.to_bytes();
|
||||
let key = Keypair::from_bytes(&b).unwrap();
|
||||
let bridge = self.bridge.clone();
|
||||
|
||||
let rpc_url = self.rpc_url.clone();
|
||||
|
||||
// we need to spawn an extra thread because tokio does not allow nested runtimes
|
||||
std::thread::spawn(move || {
|
||||
let rpc = RpcClient::new(rpc_url);
|
||||
|
||||
let vaa = &request.get_ref().vaa.as_ref().unwrap();
|
||||
|
||||
let mut emitter_address = [0u8; 32];
|
||||
emitter_address.copy_from_slice(vaa.emitter_address.as_slice());
|
||||
let post_data = PostVAAData {
|
||||
version: vaa.version as u8,
|
||||
guardian_set_index: vaa.guardian_set_index,
|
||||
timestamp: vaa.timestamp.as_ref().unwrap().seconds as u32,
|
||||
nonce: vaa.nonce,
|
||||
emitter_chain: vaa.emitter_chain as u16,
|
||||
emitter_address: emitter_address,
|
||||
sequence: vaa.sequence,
|
||||
consistency_level: vaa.consistency_level as u8,
|
||||
payload: vaa.payload.clone(),
|
||||
};
|
||||
|
||||
let (verify_txs, signature_set) =
|
||||
pack_sig_verification_txs(&rpc, &bridge, &post_data, &vaa.signatures, &key)?;
|
||||
|
||||
// Strip signatures
|
||||
let ix = post_vaa(bridge, key.pubkey(), signature_set, post_data);
|
||||
|
||||
for mut tx in verify_txs {
|
||||
match sign_and_send(&rpc, &mut tx, vec![&key], request.get_ref().skip_preflight) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::Internal,
|
||||
format!("tx sending failed: {:?}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut transaction2 = Transaction::new_with_payer(&[ix], Some(&key.pubkey()));
|
||||
match sign_and_send(
|
||||
&rpc,
|
||||
&mut transaction2,
|
||||
vec![&key],
|
||||
request.into_inner().skip_preflight,
|
||||
) {
|
||||
Ok(s) => Ok(Response::new(SubmitVaaResponse {
|
||||
signature: s.to_string(),
|
||||
})),
|
||||
Err(e) => Err(Status::new(
|
||||
Code::Internal,
|
||||
format!("tx sending failed: {:?}", e),
|
||||
)),
|
||||
}
|
||||
})
|
||||
.join()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn get_balance(
|
||||
&self,
|
||||
_request: Request<GetBalanceRequest>,
|
||||
) -> Result<Response<GetBalanceResponse>, Status> {
|
||||
// Hack to clone keypair
|
||||
let b = self.key.pubkey();
|
||||
|
||||
let rpc_url = self.rpc_url.clone();
|
||||
|
||||
// we need to spawn an extra thread because tokio does not allow nested runtimes
|
||||
std::thread::spawn(move || {
|
||||
let rpc = RpcClient::new(rpc_url);
|
||||
|
||||
let balance = match rpc.get_balance(&b) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::Internal,
|
||||
format!("failed to fetch balance: {:?}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(GetBalanceResponse { balance }))
|
||||
})
|
||||
.join()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn pack_sig_verification_txs<'a>(
|
||||
rpc: &RpcClient,
|
||||
bridge: &Pubkey,
|
||||
vaa: &PostVAAData,
|
||||
signatures: &Vec<service::Signature>,
|
||||
sender_keypair: &'a Keypair,
|
||||
) -> Result<(Vec<Transaction>, Pubkey), Status> {
|
||||
let signature_set = Keypair::new();
|
||||
// Load guardian set
|
||||
let guardian_key = GuardianSet::<'_, { AccountState::Initialized }>::key(
|
||||
&GuardianSetDerivationData {
|
||||
index: vaa.guardian_set_index,
|
||||
},
|
||||
bridge,
|
||||
);
|
||||
let guardian_account = rpc
|
||||
.get_account_with_commitment(
|
||||
&guardian_key,
|
||||
CommitmentConfig {
|
||||
commitment: CommitmentLevel::Processed,
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap_or_default();
|
||||
let data = guardian_account.data;
|
||||
let guardian_set: GuardianSetData = GuardianSetData::try_from_slice(data.as_slice()).unwrap();
|
||||
|
||||
// Map signatures to guardian set
|
||||
let mut signature_items: Vec<SignatureItem> = Vec::new();
|
||||
for s in signatures.iter() {
|
||||
let mut item = SignatureItem {
|
||||
signature: s.signature.clone(),
|
||||
key: [0; 20],
|
||||
index: s.guardian_index as u8,
|
||||
};
|
||||
item.key = guardian_set.keys[s.guardian_index as usize];
|
||||
|
||||
signature_items.push(item);
|
||||
}
|
||||
|
||||
let vaa_body = serialize_vaa(vaa);
|
||||
let body_hash: [u8; 32] = {
|
||||
let mut h = sha3::Keccak256::default();
|
||||
h.write(vaa_body.as_slice())?;
|
||||
h.finalize().into()
|
||||
};
|
||||
|
||||
let mut verify_txs: Vec<Transaction> = Vec::new();
|
||||
for (_tx_index, chunk) in signature_items.chunks(6).enumerate() {
|
||||
let mut secp_payload = Vec::new();
|
||||
let mut signature_status = [-1i8; 19];
|
||||
|
||||
let data_offset = 1 + chunk.len() * 11;
|
||||
let message_offset = data_offset + chunk.len() * 85;
|
||||
|
||||
// 1 number of signatures
|
||||
secp_payload.write_u8(chunk.len() as u8)?;
|
||||
|
||||
// Secp signature info description (11 bytes * n)
|
||||
for (i, s) in chunk.iter().enumerate() {
|
||||
secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i) as u16)?;
|
||||
secp_payload.write_u8(0)?;
|
||||
secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i + 65) as u16)?;
|
||||
secp_payload.write_u8(0)?;
|
||||
secp_payload.write_u16::<LittleEndian>(message_offset as u16)?;
|
||||
secp_payload.write_u16::<LittleEndian>(body_hash.len() as u16)?;
|
||||
secp_payload.write_u8(0)?;
|
||||
signature_status[s.index as usize] = i as i8;
|
||||
}
|
||||
|
||||
// Write signatures and addresses
|
||||
for s in chunk.iter() {
|
||||
secp_payload.write(&s.signature)?;
|
||||
secp_payload.write(&s.key)?;
|
||||
}
|
||||
|
||||
// Write body
|
||||
secp_payload.write(&body_hash)?;
|
||||
|
||||
let secp_ix = Instruction {
|
||||
program_id: solana_sdk::secp256k1_program::id(),
|
||||
data: secp_payload,
|
||||
accounts: vec![],
|
||||
};
|
||||
|
||||
let body_hash: [u8; 32] = hash_vaa(vaa);
|
||||
|
||||
let payload = VerifySignaturesData {
|
||||
signers: signature_status,
|
||||
};
|
||||
|
||||
let verify_ix = match verify_signatures(
|
||||
*bridge,
|
||||
sender_keypair.pubkey(),
|
||||
vaa.guardian_set_index,
|
||||
signature_set.pubkey(),
|
||||
payload,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(Status::new(
|
||||
Code::InvalidArgument,
|
||||
format!("could not create verify instruction: {:?}", e),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
verify_txs.push(Transaction::new_with_payer(
|
||||
&[secp_ix, verify_ix],
|
||||
Some(&sender_keypair.pubkey()),
|
||||
))
|
||||
}
|
||||
|
||||
Ok((verify_txs, signature_set.pubkey()))
|
||||
}
|
||||
|
||||
fn sign_and_send(
|
||||
rpc: &RpcClient,
|
||||
tx: &mut Transaction,
|
||||
keys: Vec<&Keypair>,
|
||||
skip_preflight: bool,
|
||||
) -> Result<Signature, ClientError> {
|
||||
let (recent_blockhash, _fee_calculator) = rpc.get_recent_blockhash()?;
|
||||
|
||||
tx.sign(&keys, recent_blockhash);
|
||||
|
||||
rpc.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
CommitmentConfig {
|
||||
commitment: CommitmentLevel::Processed,
|
||||
},
|
||||
RpcSendTransactionConfig {
|
||||
skip_preflight,
|
||||
preflight_commitment: Some(CommitmentLevel::Processed),
|
||||
encoding: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let matches = App::new("Wormhole Solana agent")
|
||||
.arg(
|
||||
Arg::with_name("bridge")
|
||||
.long("bridge")
|
||||
.value_name("ADDRESS")
|
||||
.help("Bridge address")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ws")
|
||||
.long("ws")
|
||||
.value_name("URI")
|
||||
.help("PubSub Websocket URI (ws[s]://)")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rpc")
|
||||
.long("rpc")
|
||||
.value_name("URI")
|
||||
.help("RPC URI (http[s]://)")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("socket")
|
||||
.long("socket")
|
||||
.value_name("FILE")
|
||||
.help("Path to agent socket")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("keypair")
|
||||
.long("keypair")
|
||||
.value_name("FILE")
|
||||
.help("Fee payer account key ")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let bridge = matches.value_of("bridge").unwrap();
|
||||
let rpc_url = matches.value_of("rpc").unwrap();
|
||||
let socket_path = matches.value_of("socket").unwrap();
|
||||
let keypair = read_keypair_file(matches.value_of("keypair").unwrap()).unwrap();
|
||||
|
||||
println!("Agent using account: {}", keypair.pubkey());
|
||||
|
||||
let agent = AgentImpl {
|
||||
rpc_url: rpc_url.to_string(),
|
||||
bridge: Pubkey::from_str(bridge).unwrap(),
|
||||
key: keypair,
|
||||
};
|
||||
|
||||
// Setting a umask appears to be the only way of safely creating a UNIX socket using
|
||||
// UnixListener::bind without introducing a TOCTOU race condition.
|
||||
unsafe { libc::umask(0o0077) };
|
||||
|
||||
// Delete existing socket file and recreate it with restrictive permissions.
|
||||
let path = Path::new(socket_path);
|
||||
if path.exists() {
|
||||
fs::remove_file(path)?;
|
||||
}
|
||||
|
||||
let mut listener = UnixListener::bind(socket_path)?;
|
||||
println!("Agent listening on {}", socket_path);
|
||||
|
||||
Server::builder()
|
||||
.add_service(AgentServer::new(agent))
|
||||
.serve_with_incoming(listener.incoming().map_ok(socket::UnixStream))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// https://github.com/hyperium/tonic/blob/2e964c78c666ecd6e6cfc37689d30300cad81f4c/examples/src/uds/server.rs#L55
|
||||
// (MIT License)
|
||||
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{
|
||||
Context,
|
||||
Poll,
|
||||
},
|
||||
};
|
||||
|
||||
use tokio::io::{
|
||||
AsyncRead,
|
||||
AsyncWrite,
|
||||
};
|
||||
use tonic::transport::server::Connected;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnixStream(pub tokio::net::UnixStream);
|
||||
|
||||
impl Connected for UnixStream {
|
||||
}
|
||||
|
||||
impl AsyncRead for UnixStream {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
Pin::new(&mut self.0).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for UnixStream {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
Pin::new(&mut self.0).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
Pin::new(&mut self.0).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
Pin::new(&mut self.0).poll_shutdown(cx)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue