solana/bridge: remove agent
Change-Id: I8fdf4e53172a422ee5d9d4dee13faf32ced1be08
This commit is contained in:
parent
723cf5fe95
commit
b02d782f1c
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["agent", "program", "client", "program_stub", "cpi_poster"]
|
members = ["program", "client", "program_stub", "cpi_poster"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[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