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(),
})),
@ -114,13 +162,13 @@ impl Agent for AgentImpl {
)),
}
})
.join()
.unwrap()
.join()
.unwrap()
}
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,30 +184,32 @@ 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()
.join()
.unwrap()
}
}
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")
.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))
.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 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

@ -152,7 +152,7 @@ mod helpers {
0,
data,
)
.unwrap()],
.unwrap()],
);
}
@ -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,
@ -155,9 +153,9 @@ fn guardian_sign_round(
// Public Key: 0x1d72877eb2d898738afe94c6101152ede0435de9
let secret_key = secp256k1::SecretKey::parse(&[
0x99, 0x70, 0x1c, 0x80, 0x5e, 0xf9, 0x38, 0xe1, 0x3f, 0x0e, 0x48, 0xf0, 0x9e, 0x2c, 0x32,
0x78, 0x91, 0xc1, 0xd8, 0x47, 0x29, 0xd1, 0x52, 0xf3, 0x01, 0xe7, 0xe6, 0x2c, 0xbf, 0x1f,
0x91, 0xc9
0x99, 0x70, 0x1c, 0x80, 0x5e, 0xf9, 0x38, 0xe1, 0x3f, 0x0e, 0x48, 0xf0, 0x9e, 0x2c, 0x32,
0x78, 0x91, 0xc1, 0xd8, 0x47, 0x29, 0xd1, 0x52, 0xf3, 0x01, 0xe7, 0xe6, 0x2c, 0xbf, 0x1f,
0x91, 0xc9
]).unwrap();
let public_key = secp256k1::PublicKey::from_secret_key(&secret_key);
@ -181,9 +179,9 @@ fn guardian_sign_round(
let signature = sig.0.serialize();
vaa.signatures.push(Signature {
index: 0,
r: signature[0..32].try_into().unwrap(),
s: signature[32..64].try_into().unwrap(),
v: sig.1.serialize(),
r: signature[0..32].try_into().unwrap(),
s: signature[32..64].try_into().unwrap(),
v: sig.1.serialize(),
});
(vaa, body, body_hash, secret_key)