Adapt solana agent for v2

Change-Id: I81fc8b959f33a157371d1c59b1d5323dfc11f1ce
This commit is contained in:
Hendrik Hofstadt 2021-06-25 12:25:17 +02:00
parent a341c2a5b5
commit 6d1b3d2651
10 changed files with 230 additions and 193 deletions

View File

@ -2,6 +2,7 @@ syntax = "proto3";
package agent.v1;
import "google/protobuf/timestamp.proto";
// TODO: documentation
option go_package = "github.com/certusone/wormhole/bridge/pkg/proto/agent/v1;agentv1";
@ -15,10 +16,27 @@ message Empty {
}
message SubmitVAARequest {
bytes vaa = 1;
VAA vaa = 1;
bool skip_preflight = 2;
}
message VAA {
uint32 Version = 1;
google.protobuf.Timestamp Timestamp = 2;
uint32 Nonce = 3;
uint32 EmitterChain = 4;
bytes EmitterAddress = 5;
uint64 Sequence = 6;
bytes Payload = 7;
uint32 GuardianSetIndex = 8;
repeated Signature Signatures = 9;
}
message Signature{
uint32 GuardianIndex = 1;
bytes Signature = 2;
}
message SubmitVAAResponse {
string signature = 1;
}

View File

@ -31,6 +31,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
name = "agent"
version = "0.1.0"
dependencies = [
"borsh",
"bridge",
"bs58",
"byteorder",
@ -46,6 +47,7 @@ dependencies = [
"serde_bytes",
"serde_derive",
"serde_json",
"sha3",
"solana-client",
"solana-program",
"solana-sdk",

View File

@ -14,7 +14,7 @@ solana-program = "=1.7.0"
solana-sdk = "=1.7.0"
solitaire = { path = "../../solitaire/program" }
solitaire-client = {path = "../../solitaire/client" }
bridge = { path = "../program", default-features = false, features = ["no-entrypoint"] }
bridge = { path = "../program", features = ["no-idl", "no-entrypoint", "client"] }
primitive-types = { version = "0.7.2" }
hex = "0.4.2"
thiserror = "1.0.20"
@ -26,10 +26,12 @@ log = "0.4.11"
serde_derive = "1.0.103"
serde_json = "1.0.57"
bs58 = "0.3.1"
byteorder = "1.3.4"
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"] }

View File

@ -1,3 +1,3 @@
fn main() {
tonic_build::compile_protos("../../proto/agent/v1/service.proto").unwrap();
tonic_build::compile_protos("../../../proto/agent/v1/service.proto").unwrap();
}

View File

@ -1,34 +1,80 @@
use std::{env, io::Write, mem::size_of, str::FromStr, fs};
use std::path::Path;
use libc;
use std::{
fs,
io::Write,
path::Path,
str::FromStr,
};
use clap::{Arg, App, SubCommand};
use clap::{
App,
Arg,
};
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
use byteorder::{
LittleEndian,
WriteBytesExt,
};
use futures::stream::TryStreamExt;
use solana_client::{
client_error::ClientError, rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig,
client_error::ClientError,
rpc_client::RpcClient,
rpc_config::RpcSendTransactionConfig,
};
use solana_sdk::{
commitment_config::{CommitmentConfig, CommitmentLevel},
commitment_config::{
CommitmentConfig,
CommitmentLevel,
},
instruction::Instruction,
pubkey::Pubkey,
signature::{read_keypair_file, write_keypair_file, Keypair, Signature, Signer},
signature::{
read_keypair_file,
Keypair,
Signature,
Signer,
},
transaction::Transaction,
};
use tokio::net::UnixListener;
use tokio::sync::mpsc;
use tonic::{transport::Server, Code, Request, Response, Status};
use service::{
agent_server::{Agent, AgentServer},
Empty,SubmitVaaRequest, SubmitVaaResponse,
GetBalanceResponse, GetBalanceRequest,
use tonic::{
transport::Server,
Code,
Request,
Response,
Status,
};
use spl_bridge::{
instruction::{post_vaa, verify_signatures, VerifySigPayload, CHAIN_ID_SOLANA},
state::{Bridge, GuardianSet, PostedMessage},
vaa::VAA,
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 solitaire::{
processors::seeded::Seeded,
AccountState,
};
mod socket;
@ -38,7 +84,6 @@ pub mod service {
}
pub struct AgentImpl {
url: String,
bridge: Pubkey,
rpc_url: String,
@ -46,7 +91,7 @@ pub struct AgentImpl {
}
pub struct SignatureItem {
signature: [u8; 64 + 1],
signature: Vec<u8>,
key: [u8; 20],
index: u8,
}
@ -68,43 +113,46 @@ impl Agent for AgentImpl {
std::thread::spawn(move || {
let rpc = RpcClient::new(rpc_url);
let mut vaa = match VAA::deserialize(&request.get_ref().vaa) {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could not parse VAA: {}", e),
));
}
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,
payload: vaa.payload.clone(),
};
let verify_txs = pack_sig_verification_txs(&rpc, &bridge, &vaa, &key)?;
let verify_txs =
pack_sig_verification_txs(&rpc, &bridge, &post_data, &vaa.signatures, &key)?;
// Strip signatures
vaa.signatures = Vec::new();
let ix = match post_vaa(&bridge, &key.pubkey(), vaa.serialize().unwrap()) {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could not create post_vaa instruction: {}", e),
));
}
};
let ix = post_vaa(bridge, key.pubkey(), post_data);
for mut tx in verify_txs {
match sign_and_send(&rpc, &mut tx, vec![&key], request.skip_preflight) {
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),
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.skip_preflight) {
match sign_and_send(
&rpc,
&mut transaction2,
vec![&key],
request.into_inner().skip_preflight,
) {
Ok(s) => Ok(Response::new(SubmitVaaResponse {
signature: s.to_string(),
})),
@ -120,7 +168,7 @@ impl Agent for AgentImpl {
async fn get_balance(
&self,
request: Request<GetBalanceRequest>,
_request: Request<GetBalanceRequest>,
) -> Result<Response<GetBalanceResponse>, Status> {
// Hack to clone keypair
let b = self.key.pubkey();
@ -136,14 +184,12 @@ impl Agent for AgentImpl {
Err(e) => {
return Err(Status::new(
Code::Internal,
format!("failed to fetch balance: {}", e),
format!("failed to fetch balance: {:?}", e),
));
}
};
Ok(Response::new(GetBalanceResponse {
balance,
}))
Ok(Response::new(GetBalanceResponse { balance }))
})
.join()
.unwrap()
@ -153,13 +199,17 @@ impl Agent for AgentImpl {
fn pack_sig_verification_txs<'a>(
rpc: &RpcClient,
bridge: &Pubkey,
vaa: &VAA,
vaa: &PostVAAData,
signatures: &Vec<service::Signature>,
sender_keypair: &'a Keypair,
) -> Result<Vec<Transaction>, Status> {
// Load guardian set
let bridge_key = Bridge::derive_bridge_id(bridge).unwrap();
let guardian_key =
Bridge::derive_guardian_set_id(bridge, &bridge_key, vaa.guardian_set_index).unwrap();
let guardian_key = GuardianSet::<'_, { AccountState::Initialized }>::key(
&GuardianSetDerivationData {
index: vaa.guardian_set_index,
},
bridge,
);
let guardian_account = rpc
.get_account_with_commitment(
&guardian_key,
@ -171,78 +221,54 @@ fn pack_sig_verification_txs<'a>(
.value
.unwrap_or_default();
let data = guardian_account.data;
let guardian_set: &GuardianSet = Bridge::unpack_immutable(data.as_slice()).unwrap();
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 vaa.signatures.iter() {
for s in signatures.iter() {
let mut item = SignatureItem {
signature: [0; 64 + 1],
signature: s.signature.clone(),
key: [0; 20],
index: s.index,
index: s.guardian_index as u8,
};
item.signature[0..32].copy_from_slice(&s.r);
item.signature[32..64].copy_from_slice(&s.s);
item.signature[64] = s.v;
item.key = guardian_set.keys[s.index as usize];
item.key = guardian_set.keys[s.guardian_index as usize];
signature_items.push(item);
}
let vaa_hash = match vaa.body_hash() {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could get vaa body hash: {}", e),
));
}
};
let vaa_body = match vaa.signature_body() {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could get vaa body: {}", e),
));
}
};
let signature_acc =
Bridge::derive_signature_id(&bridge, &bridge_key, &vaa_hash, guardian_set.index).unwrap();
let vaa_body = serialize_vaa(vaa);
let mut verify_txs: Vec<Transaction> = Vec::new();
for (tx_index, chunk) in signature_items.chunks(6).enumerate() {
for (_tx_index, chunk) in signature_items.chunks(6).enumerate() {
let mut secp_payload = Vec::new();
let mut signature_status = [-1i8; 20];
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_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>(vaa_body.len() as u16);
secp_payload.write_u8(0);
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>(vaa_body.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);
secp_payload.write(&s.signature)?;
secp_payload.write(&s.key)?;
}
// Write body
secp_payload.write(&vaa_body);
secp_payload.write(&vaa_body)?;
let secp_ix = Instruction {
program_id: solana_sdk::secp256k1_program::id(),
@ -250,24 +276,25 @@ fn pack_sig_verification_txs<'a>(
accounts: vec![],
};
let payload = VerifySigPayload {
let body_hash: [u8; 32] = hash_vaa(vaa);
let payload = VerifySignaturesData {
signers: signature_status,
hash: vaa_hash,
hash: body_hash,
initial_creation: false,
};
let verify_ix = match verify_signatures(
&bridge,
&signature_acc,
&sender_keypair.pubkey(),
*bridge,
sender_keypair.pubkey(),
vaa.guardian_set_index,
&payload,
payload,
) {
Ok(v) => v,
Err(e) => {
return Err(Status::new(
Code::InvalidArgument,
format!("could not create verify instruction: {}", e),
format!("could not create verify instruction: {:?}", e),
));
}
};
@ -307,40 +334,49 @@ fn sign_and_send(
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = App::new("Wormhole Solana agent")
.arg(Arg::with_name("bridge")
.arg(
Arg::with_name("bridge")
.long("bridge")
.value_name("ADDRESS")
.help("Bridge address")
.required(true)
.takes_value(true))
.arg(Arg::with_name("ws")
.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")
.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")
.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")
.takes_value(true),
)
.arg(
Arg::with_name("keypair")
.long("keypair")
.value_name("FILE")
.help("Fee payer account key ")
.required(true)
.takes_value(true))
.takes_value(true),
)
.get_matches();
let bridge = matches.value_of("bridge").unwrap();
let ws_url = matches.value_of("ws").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();
@ -348,7 +384,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Agent using account: {}", keypair.pubkey());
let agent = AgentImpl {
url: ws_url.to_string(),
rpc_url: rpc_url.to_string(),
bridge: Pubkey::from_str(bridge).unwrap(),
key: keypair,
@ -359,7 +394,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
unsafe { libc::umask(0o0077) };
// Delete existing socket file and recreate it with restrictive permissions.
let mut path = Path::new(socket_path);
let path = Path::new(socket_path);
if path.exists() {
fs::remove_file(path)?;
}

View File

@ -100,7 +100,6 @@ pub struct PostVAAData {
// Header part
pub version: u8,
pub guardian_set_index: u32,
pub signatures: Vec<Signature>,
// Body part
pub timestamp: u32,

View File

@ -1,18 +1,10 @@
use borsh::BorshSerialize;
use solana_program::{
borsh::try_from_slice_unchecked,
hash,
instruction::{
AccountMeta,
Instruction,
},
program_pack::Pack,
pubkey::Pubkey,
system_instruction::{
self,
create_account,
},
system_program,
sysvar,
};
@ -34,7 +26,6 @@ use crate::{
SignatureSet,
SignatureSetDerivationData,
},
types::PostedMessage,
BridgeConfig,
PostMessageData,
PostVAAData,
@ -124,7 +115,7 @@ pub fn verify_signatures(
program_id: Pubkey,
payer: Pubkey,
guardian_set_index: u32,
hash: [u8; 32],
data: VerifySignaturesData,
) -> solitaire::Result<Instruction> {
let guardian_set = GuardianSet::<'_, { AccountState::Uninitialized }>::key(
&GuardianSetDerivationData {
@ -134,15 +125,10 @@ pub fn verify_signatures(
);
let signature_set = SignatureSet::<'_, { AccountState::Uninitialized }>::key(
&SignatureSetDerivationData { hash },
&SignatureSetDerivationData { hash: data.hash },
&program_id,
);
// Bridge with a single pre-existing signer.
// TODO: Get rid of this, exists to make testing easier for now.
let mut signers = [-1; 19];
signers[0] = 0;
Ok(Instruction {
program_id,
@ -155,40 +141,31 @@ pub fn verify_signatures(
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: crate::instruction::Instruction::VerifySignatures(VerifySignaturesData {
hash,
signers,
initial_creation: true,
})
.try_to_vec()?,
data: crate::instruction::Instruction::VerifySignatures(data).try_to_vec()?,
})
}
pub fn post_vaa(
program_id: Pubkey,
payer: Pubkey,
emitter: Pubkey,
guardian_set_index: u32,
vaa: PostVAAData,
) -> Instruction {
pub fn post_vaa(program_id: Pubkey, payer: Pubkey, vaa: PostVAAData) -> Instruction {
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let guardian_set = GuardianSet::<'_, { AccountState::Uninitialized }>::key(
&GuardianSetDerivationData {
index: guardian_set_index,
index: vaa.guardian_set_index,
},
&program_id,
);
let signature_set = SignatureSet::<'_, { AccountState::Uninitialized }>::key(
&SignatureSetDerivationData { hash: hash_vaa(&vaa) },
&SignatureSetDerivationData {
hash: hash_vaa(&vaa),
},
&program_id,
);
let message = Message::<'_, { AccountState::MaybeInitialized }>::key(
&MessageDerivationData {
emitter_key: emitter.to_bytes(),
emitter_chain: 1,
nonce: 0,
emitter_key: vaa.emitter_address,
emitter_chain: vaa.emitter_chain,
nonce: vaa.nonce,
payload: vaa.payload.clone(),
},
&program_id,
@ -216,7 +193,7 @@ pub fn post_vaa(
// Convert a full VAA structure into the serialization of its unique components, this structure is
// what is hashed and verified by Guardians.
fn serialize_vaa(vaa: &PostVAAData) -> Vec<u8> {
pub fn serialize_vaa(vaa: &PostVAAData) -> Vec<u8> {
use byteorder::{
BigEndian,
WriteBytesExt,
@ -231,12 +208,13 @@ fn serialize_vaa(vaa: &PostVAAData) -> Vec<u8> {
v.write_u32::<BigEndian>(vaa.nonce).unwrap();
v.write_u16::<BigEndian>(vaa.emitter_chain).unwrap();
v.write(&vaa.emitter_address).unwrap();
v.write_u64::<BigEndian>(vaa.sequence).unwrap();
v.write(&vaa.payload).unwrap();
v.into_inner()
}
// Hash a VAA, this combines serialization and hashing.
fn hash_vaa(vaa: &PostVAAData) -> [u8; 32] {
pub fn hash_vaa(vaa: &PostVAAData) -> [u8; 32] {
use sha3::Digest;
use std::io::Write;

View File

@ -1,5 +1,6 @@
#![feature(const_generics)]
#![allow(non_upper_case_globals)]
#![allow(incomplete_features)]
use solana_program::msg;

View File

@ -164,13 +164,20 @@ mod helpers {
body_hash: [u8; 32],
secret_key: SecretKey,
) {
let mut signers = [-1; 19];
signers[0] = 0;
execute(
client,
payer,
&[payer],
&[
new_secp256k1_instruction(&secret_key, &body),
instructions::verify_signatures(*program, payer.pubkey(), 0, body_hash).unwrap(),
instructions::verify_signatures(*program, payer.pubkey(), 0, VerifySignaturesData {
hash: body_hash,
signers,
initial_creation: true,
}).unwrap(),
],
);
}
@ -179,7 +186,6 @@ mod helpers {
client: &RpcClient,
program: &Pubkey,
payer: &Keypair,
emitter: &Pubkey,
vaa: PostVAAData,
) {
execute(
@ -189,8 +195,6 @@ mod helpers {
&[instructions::post_vaa(
*program,
payer.pubkey(),
*emitter,
0,
vaa,
)],
);

View File

@ -112,7 +112,6 @@ fn test_bridge_messages() {
client,
program,
payer,
&emitter.pubkey(),
vaa,
);
@ -123,12 +122,11 @@ fn test_bridge_messages() {
/// is on the chain and creating a signature set for it.
fn guardian_sign_round(
emitter: &Keypair,
data: Vec<u8>
data: Vec<u8>,
) -> (PostVAAData, Vec<u8>, [u8; 32], secp256k1::SecretKey) {
let mut vaa = PostVAAData {
version: 0,
guardian_set_index: 0,
signatures: vec![],
// Body part
nonce: 0,