Bitcoin Payment Verification Program (#5153)

* btc_spv program directories

* add spv-instruction spv-state

* added spv_processor file

* cargo.tomls - bump versions, rm unneccessary deps

* add btc_spv_bin and top lvl workspace entry

* hex_decode util & errors

* add header parsing test

* update dependencies

* rustfmt

* refactor Requests

* fix dependencies/versions

* clippy fixes

* test improvements

* add gitignores

Add framework for the rest of the BTC-SPV stuff to be built on top of. This PR defines the components, data structures, accessors, etc. but is not quite complete. It still needs the headerstore component finished along with some of the validation utils, hashing stuff, and more tests.
This commit is contained in:
Patrick Amato 2019-09-03 19:16:02 -06:00 committed by GitHub
parent 8362b408d9
commit 2b696ac8dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 957 additions and 0 deletions

38
Cargo.lock generated
View File

@ -291,6 +291,16 @@ dependencies = [
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "btc_spv_bin"
version = "0.1.0"
dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "build_const"
version = "0.2.1"
@ -3127,6 +3137,34 @@ dependencies = [
"solana-sdk 0.19.0-pre0",
]
[[package]]
name = "solana-bitcoin-spv-api"
version = "0.0.1"
dependencies = [
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-sdk 0.19.0-pre0",
]
[[package]]
name = "solana-bitcoin-spv-program"
version = "0.0.1"
dependencies = [
"bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-sdk 0.19.0-pre0",
]
[[package]]
name = "solana-bpf-loader-api"
version = "0.19.0-pre0"

View File

@ -26,6 +26,9 @@ members = [
"programs/bpf_loader_program",
"programs/budget_api",
"programs/budget_program",
"programs/btc_spv_program",
"programs/btc_spv_api",
"programs/btc_spv_bin",
"programs/config_api",
"programs/config_program",
"programs/config_tests",

2
programs/btc_spv_api/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target/
/farf/

View File

@ -0,0 +1,23 @@
[package]
name = "solana-btc-spv-api"
version = "0.19.0-pre0"
description = "Solana Bitcoin spv parsing program api"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
bincode = "1.1.4"
chrono = { version = "0.4.0", features = ["serde"] }
log = "0.4.2"
num-derive = "0.2"
num-traits = "0.2"
serde = "1.0.99"
serde_derive = "1.0.98"
solana-sdk = { path = "../../sdk", version = "0.19.0-pre0"}
[lib]
crate-type = ["lib"]
name = "solana_btc_spv_api"

View File

@ -0,0 +1,64 @@
## Problem
Inter-chain applications are not new to the digital asset ecosystem; in fact, even
the smaller centralized exchanges still categorically dwarf all single chain dapps
put together in terms of users and volume. They command massive valuations and
have spent years effectively optimizing their core products for a broad range of
end users. However, their basic operations center around mechanisms that require
their users to unilaterally trust them, typically with little to no recourse
or protection from accidental loss. This has led to the broader digital asset
ecosystem being fractured along network lines because interoperability solutions typically:
* Are technically complex to fully implement
* Create unstable network scale incentive structures
* Require consistent and high level cooperation between stakeholders
## Proposed Solution
Simple Payment Verification (SPV) is a generic term for a range of different
methodologies used by light clients on most major blockchain networks to verify
aspects of the network state without the burden of fully storing and maintaining
the chain itself. In most cases, this means relying on a form of hash tree to
supply a proof of the presence of a given transaction in a certain block by
comparing against a root hash in that blocks header or equivalent. This allows
a light client or wallet to reach a probabilistic level of certainty about
on-chain events by itself with a minimum of trust required with regard to network nodes.
Traditionally the process of assembling and validating these proofs is carried
out off chain by nodes, wallets, or other clients, but it also offers a potential
mechanism for inter-chain state verification. However, by moving the capability
to validate SPV proofs on-chain as a smart contract while leveraging the archival
properties inherent to the blockchain, it is possible to construct a system for
programmatically detecting and verifying transactions on other networks without
the involvement of any type of trusted oracle or complex multi-stage consensus
mechanism. This concept is broadly generalisable to any network with an SPV
mechanism and can even be operated bilaterally on other smart contract platforms,
opening up the possibility of cheap, fast, inter-chain transfer of value without
relying on collateral, hashlocks, or trusted intermediaries.
Opting to take advantage of well established and developmentally stable mechanisms
already common to all major blockchains allows SPV based interoperability solutions
to be dramatically simpler than orchestrated multi-stage approaches. As part of
this, they dispense with the need for widely agreed upon cross chain communication
standards and the large multi-party organizations that write them in favor of a
set of discrete contract-based services that can be easily utilized by caller
contracts through a common abstraction format. This will set the groundwork for
a broad range of dapps and contracts able to interoperate across the variegated
and every growing platform ecosystem.
## Terminology
SPV Program - Client-facing interface for the inter-chain SPV system, manages participant roles.
SPV Engine - Validates transaction proofs, subset of the SPV Program.
Client - The caller to the SPV Program, typically another solana contract.
Prover - Party who generates proofs for transactions and submits them to the SPV Program.
Transaction Proof - Created by Provers, contains a merkle proof, transaction, and blockheader reference.
Merkle Proof - Basic SPV proof that validates the presence of a transaction in a certain block.
Block Header - Represents the basic parameters and relative position of a given block.
Proof Request - An order placed by a client for verification of transaction(s) by provers.
Header Store - A data structure for storing and referencing ranges of block headers in proofs.
Client Request - Transaction from the client to the SPV Program to trigger creation of a Proof Request.
Sub-account - A Solana account owned by another contract account, without its own private key.
For more information on the Inter-chain SPV system, see the book section at:
https://solana-labs.github.io/book/interchain-transaction-verification.html

View File

@ -0,0 +1,102 @@
#[allow(unused_imports)]
use crate::spv_state::*;
use serde_derive::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
// HeaderStore is a data structure that allows linked list style cheap appends and
// sequential reads, but also has a "lookup index" to speed up random access
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum HeaderStoreError {
InvalidHeader,
GroupExists,
GroupDNE,
InvalidBlockHeight,
}
// AccountList is a linked list of groups of blockheaders. It stores sequential blockheaders
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct HeaderStore {
pub index: Vec<Pubkey>,
// number of header entries to include per group account
pub group_size: u16,
// base_height is the height of the first header in the first headerAccount
pub base_height: u32,
// top_height is the running last header loaded
pub top_height: u32,
// account that administrates the headerstore and benefits from fees accrued
pub owner: Pubkey,
}
impl HeaderStore {
pub fn get_group(self, block_height: u32) -> Result<Pubkey, HeaderStoreError> {
if block_height < self.base_height || block_height > self.top_height {
Err(HeaderStoreError::InvalidBlockHeight)
} else {
let gheight = (block_height - self.base_height) / u32::from(self.group_size);
let grouppk: Pubkey = self.index[gheight as usize];
Ok(grouppk)
}
}
pub fn top_group(self) -> Result<Pubkey, HeaderStoreError> {
if self.index.is_empty() {
Err(HeaderStoreError::GroupDNE)
} else {
let grouppk: Pubkey = *self.index.last().unwrap();
Ok(grouppk)
}
}
pub fn append_header(mut self, blockheader: &BlockHeader) -> Result<(), HeaderStoreError> {
match self.top_group() {
Ok(n) => {
let group = n;
Ok(())
}
Err(e) => {
// HeaderStore is empty need to create first group
if HeaderStoreError::GroupDNE == e {
Ok(())
} else {
Err(e)
}
//reinsert
}
}
}
pub fn replace_header(
mut self,
blockheader: &BlockHeader,
block_height: u32,
) -> Result<(), HeaderStoreError> {
match self.get_group(block_height) {
Err(e) => Err(HeaderStoreError::InvalidHeader),
Ok(n) => {
let group = n;
Ok(())
}
}
//reinsert
}
pub fn append_group(mut self, pubkey: Pubkey) -> Result<(), HeaderStoreError> {
if self.index.contains(&pubkey) {
// group to be appended is already in the index
Err(HeaderStoreError::GroupExists)
} else {
Ok(())
//reinsert
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct HeaderAccountInfo {
// parent stores the pubkey of the parent AccountList
pub parent: Pubkey,
// stores a vec of BlockHeader structs
pub headers: Vec<BlockHeader>,
// next DataAccount in the chain or none if last
pub next: Option<Pubkey>,
}

View File

@ -0,0 +1,20 @@
#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(unused_mut)]
#![allow(dead_code)]
// This version is a work in progress and contains placeholders and incomplete components
pub mod header_store;
pub mod spv_instruction;
pub mod spv_processor;
pub mod spv_state;
pub mod utils;
pub const BTC_SPV_PROGRAM_ID: [u8; 32] = [
2, 202, 42, 59, 228, 51, 182, 147, 162, 245, 234, 78, 205, 37, 131, 154, 110, 252, 154, 254,
190, 13, 90, 231, 198, 144, 239, 96, 0, 0, 0, 0,
];
solana_sdk::solana_name_id!(
BTC_SPV_PROGRAM_ID,
"BtcSpv1111111111111111111111111111111111111"
);

View File

@ -0,0 +1,78 @@
//! Spv proof Verification Program
use crate::id;
use crate::spv_state::*;
use serde_derive::{Deserialize, Serialize};
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::pubkey::Pubkey;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum SpvInstruction {
// Client Places request for a matching proof
// key 0 - Signer
// key 1 - Account in which to record the Request and proof
ClientRequest(ClientRequestInfo),
// Used by clients to cancel a pending proof request
// key 0 - signer
// key 1 - Request to cancel
CancelRequest,
// used to submit a proof matching a posted BitcoinTxHash or for own benefit
// key 0 - signer
// key 1 - Request to prove
SubmitProof(Proof),
}
pub fn client_request(
owner: &Pubkey,
txhash: BitcoinTxHash,
fee: u64,
confirmations: u8,
difficulty: u64,
expiration: Option<u32>,
) -> Instruction {
let account_meta = vec![AccountMeta::new(*owner, true)];
Instruction::new(
id(),
&SpvInstruction::ClientRequest(ClientRequestInfo {
txhash,
confirmations,
fee,
difficulty,
expiration,
}),
account_meta,
)
}
pub fn cancel_request(owner: &Pubkey, request: &Pubkey) -> Instruction {
let account_meta = vec![
AccountMeta::new(*owner, true),
AccountMeta::new(*request, false),
];
Instruction::new(id(), &SpvInstruction::CancelRequest, account_meta)
}
pub fn submit_proof(
submitter: &Pubkey,
proof: MerkleProof,
headers: HeaderChain,
transaction: Transaction,
request: &Pubkey,
) -> Instruction {
let account_meta = vec![
AccountMeta::new(*submitter, true),
AccountMeta::new(*request, false),
];
Instruction::new(
id(),
&SpvInstruction::SubmitProof(Proof {
submitter: *submitter,
proof,
headers,
transaction,
request: *request,
}),
account_meta,
)
}

View File

@ -0,0 +1,166 @@
//! Bitcoin SPV proof verifier program
//! Receive merkle proofs and block headers, validate transaction
use crate::spv_instruction::*;
use crate::spv_state::*;
#[allow(unused_imports)]
use crate::utils::decode_hex;
use log::*;
use solana_sdk::account::KeyedAccount;
use solana_sdk::instruction::InstructionError;
use solana_sdk::pubkey::Pubkey;
pub struct SpvProcessor {}
impl SpvProcessor {
pub fn validate_header_chain(
headers: HeaderChain,
proof_req: &ProofRequest,
) -> Result<(), InstructionError> {
// disabled for time being
//not done yet, needs difficulty average/variance checking still
Ok(())
}
#[allow(clippy::needless_pass_by_value)]
fn map_to_invalid_arg(err: std::boxed::Box<bincode::ErrorKind>) -> InstructionError {
warn!("Deserialize failed, not a valid state: {:?}", err);
InstructionError::InvalidArgument
}
fn deserialize_proof(data: &[u8]) -> Result<Proof, InstructionError> {
let proof_state: AccountState =
bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
if let AccountState::Verification(proof) = proof_state {
Ok(proof)
} else {
error!("Not a valid proof");
Err(InstructionError::InvalidAccountData)
}
}
fn deserialize_request(data: &[u8]) -> Result<ClientRequestInfo, InstructionError> {
let req_state: AccountState =
bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
if let AccountState::Request(info) = req_state {
Ok(info)
} else {
error!("Not a valid proof request");
Err(InstructionError::InvalidAccountData)
}
}
pub fn check_account_unallocated(data: &[u8]) -> Result<(), InstructionError> {
let acct_state: AccountState =
bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
if let AccountState::Unallocated = acct_state {
Ok(())
} else {
error!("Provided account is already occupied");
Err(InstructionError::InvalidAccountData)
}
}
pub fn do_client_request(
keyed_accounts: &mut [KeyedAccount],
request_info: &ClientRequestInfo,
) -> Result<(), InstructionError> {
if keyed_accounts.len() != 2 {
error!("Client Request invalid accounts argument length (should be 2)")
}
const OWNER_INDEX: usize = 0;
const REQUEST_INDEX: usize = 1;
// check_account_unallocated(&keyed_accounts[REQUEST_INDEX].account.data)?;
Ok(()) //placeholder
}
pub fn do_cancel_request(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> {
if keyed_accounts.len() != 2 {
error!("Client Request invalid accounts argument length (should be 2)")
}
const OWNER_INDEX: usize = 0;
const CANCEL_INDEX: usize = 1;
Ok(()) //placeholder
}
pub fn do_submit_proof(
keyed_accounts: &mut [KeyedAccount],
proof_info: &Proof,
) -> Result<(), InstructionError> {
if keyed_accounts.len() != 2 {
error!("Client Request invalid accounts argument length (should be 2)")
}
const SUBMITTER_INDEX: usize = 0;
const PROOF_REQUEST_INDEX: usize = 1;
Ok(()) //placeholder
}
}
pub fn process_instruction(
_program_id: &Pubkey,
keyed_accounts: &mut [KeyedAccount],
data: &[u8],
) -> Result<(), InstructionError> {
// solana_logger::setup();
let command = bincode::deserialize::<SpvInstruction>(data).map_err(|err| {
info!("invalid instruction data: {:?} {:?}", data, err);
InstructionError::InvalidInstructionData
})?;
trace!("{:?}", command);
match command {
SpvInstruction::ClientRequest(client_request_info) => {
SpvProcessor::do_client_request(keyed_accounts, &client_request_info)
}
SpvInstruction::CancelRequest => SpvProcessor::do_cancel_request(keyed_accounts),
SpvInstruction::SubmitProof(proof_info) => {
SpvProcessor::do_submit_proof(keyed_accounts, &proof_info)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{spv_instruction, spv_state, utils};
#[test]
fn test_parse_header_hex() -> Result<(), SpvError> {
let testheader = "010000008a730974ac39042e95f82d719550e224c1a680a8dc9e8df9d007000000000000f50b20e8720a552dd36eb2ebdb7dceec9569e0395c990c1eb8a4292eeda05a931e1fce4e9a110e1a7a58aeb0";
let testhash = "0000000000000bae09a7a393a8acded75aa67e46cb81f7acaa5ad94f9eacd103";
let testheaderbytes = decode_hex(&testheader)?;
let testhashbytes = decode_hex(&testhash)?;
let mut blockhash: [u8; 32] = [0; 32];
blockhash.copy_from_slice(&testhashbytes[..32]);
let mut version: [u8; 4] = [0; 4];
version.copy_from_slice(&testheaderbytes[..4]);
let test_version = u32::from_le_bytes(version);
let mut test_parent: [u8; 32] = [0; 32];
test_parent.copy_from_slice(&testheaderbytes[4..36]);
let mut merkleroot: [u8; 32] = [0; 32];
merkleroot.copy_from_slice(&testheaderbytes[36..68]);
let mut time: [u8; 4] = [0; 4];
time.copy_from_slice(&testheaderbytes[68..72]);
let test_time = u32::from_le_bytes(time);
let mut test_nonce: [u8; 4] = [0; 4];
test_nonce.copy_from_slice(&testheaderbytes[76..80]);
let bh = BlockHeader::hexnew(&testheader, &testhash)?;
assert_eq!(bh.blockhash, blockhash);
assert_eq!(bh.merkle_root.hash, merkleroot);
assert_eq!(bh.version, test_version);
assert_eq!(bh.time, test_time);
assert_eq!(bh.parent, test_parent);
assert_eq!(bh.nonce, test_nonce);
Ok(())
}
}

View File

@ -0,0 +1,295 @@
use crate::header_store::*;
use crate::utils::*;
use serde_derive::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
use std::{error, fmt};
pub type BitcoinTxHash = [u8; 32];
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct BlockHeader {
// Bitcoin network version
pub version: u32,
// Previous block's hash/digest
pub parent: [u8; 32],
// merkle Root of the block, proofEntry side should be None
pub merkle_root: ProofEntry,
// the blocktime associate with the block
pub time: u32,
// An encoded version of the target threshold this blocks header hash must be less than or equal to.
pub nbits: [u8; 4],
// block header's nonce
pub nonce: [u8; 4],
// Block hash
pub blockhash: [u8; 32],
}
impl BlockHeader {
pub fn new(header: &[u8; 80], blockhash: &[u8; 32]) -> Result<BlockHeader, SpvError> {
let mut va: [u8; 4] = [0; 4];
va.copy_from_slice(&header[0..4]);
let version = u32::from_le_bytes(va);
let mut ph: [u8; 32] = [0; 32];
ph.copy_from_slice(&header[4..36]);
let parent = ph;
// extract merkle root in internal byte order
let mut mrr: [u8; 32] = [0; 32];
mrr.copy_from_slice(&header[36..68]);
let merkle_root = ProofEntry {
hash: mrr,
side: EntrySide::Root,
};
// timestamp associate with the block
let mut bt: [u8; 4] = [0; 4];
bt.copy_from_slice(&header[68..72]);
let time = u32::from_le_bytes(bt);
// nbits field is an encoded version of the
let mut nb: [u8; 4] = [0; 4];
nb.copy_from_slice(&header[72..76]);
let nbits = nb;
let mut nn: [u8; 4] = [0; 4];
nn.copy_from_slice(&header[76..80]);
let nonce = nn;
let bh = BlockHeader {
version,
parent,
merkle_root,
time,
nbits,
nonce,
blockhash: *blockhash,
};
Ok(bh)
}
pub fn hexnew(header: &str, blockhash: &str) -> Result<BlockHeader, SpvError> {
if header.len() != 160 || blockhash.len() != 64 {
return Err(SpvError::InvalidBlockHeader);
}
match decode_hex(header) {
Ok(header) => {
let bhbytes = decode_hex(blockhash)?;
const SIZE: usize = 80;
let mut hh = [0; SIZE];
hh.copy_from_slice(&header[..header.len()]);
let mut bhb: [u8; 32] = [0; 32];
bhb.copy_from_slice(&bhbytes[..bhbytes.len()]);
Ok(BlockHeader::new(&hh, &bhb).unwrap())
}
Err(e) => Err(SpvError::InvalidBlockHeader),
}
}
pub fn difficulty(mut self) -> u32 {
// calculates difficulty from nbits
let standin: u32 = 123_456_789;
standin
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Transaction {
inputs: Vec<Input>,
//input utxos
outputs: Vec<Output>,
//output utxos
version: u32,
//bitcoin network version
locktime: u32,
}
// impl Transaction {
// fn new(bytes: Vec<u8>) -> Self {
// //reinsert later
// }
// fn hexnew(hex: String) -> Self {
// //reinsert later
// }
// }
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Input {
r#type: InputType,
// Type of the input
position: u32,
// position of the tx in its Block
txhash: BitcoinTxHash,
// hash of the transaction
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum InputType {
LEGACY,
COMPATIBILITY,
WITNESS,
NONE,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Output {
r#type: OutputType,
// type of the output
value: u64,
// amount of btc in sats
payload: Vec<u8>, // data sent with the transaction
}
#[allow(non_camel_case_types)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum OutputType {
WPKH,
WSH,
OP_RETURN,
PKH,
SH,
NONSTANDARD,
}
pub type HeaderChain = Vec<BlockHeader>;
// a vector of BlockHeaders used as part of a Proof
// index 0 : the block header of the block prior to the proof Block
// index 1 : the block header of the proof block
// index 2-n* : the block headers for the confirmation chain
// (where n is the confirmations value from the proof request)
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct ProofEntry {
// 32 byte merkle hashes
pub hash: [u8; 32],
// side of the merkle tree entry
pub side: EntrySide,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum EntrySide {
// Left side of the hash combination
Left,
// Right side of hash combination
Right,
// Root hash (neither side)
Root,
}
pub type MerkleProof = Vec<ProofEntry>;
// a vector of ProofEntries used as part of a Proof
// index 0 : a ProofEntry representing the txid
// indices 0-n : ProofEntries linking the txhash and the merkle root
// index n : a ProofEntry representing the merkel root for the block in question
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct ClientRequestInfo {
// bitcoin transaction hash
pub txhash: BitcoinTxHash,
// confirmation count
pub confirmations: u8,
// fee paid for tx verification
pub fee: u64,
// required minimum difficulty for submitted blocks
pub difficulty: u64,
// expiration slot height
pub expiration: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct ProofRequest {
pub owner: Pubkey,
// bitcoin transaction hash
pub txhash: BitcoinTxHash,
// confirmation count
pub confirmations: u8,
// fee paid for tx verification
pub fee: u64,
// minimum allowable difficulty
pub difficulty: u64,
// expiration slot height
pub expiration: u64,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Proof {
// the pubkey who submitted the proof in question, entitled to fees from any corresponding proof requests
pub submitter: Pubkey,
// merkle branch connecting txhash to block header merkle root
pub proof: MerkleProof,
// chain of bitcoin headers provifing context for the proof
pub headers: HeaderChain,
// transaction associated with the Proof
pub transaction: Transaction,
// public key of the request this proof corresponds to
pub request: Pubkey,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum AccountState {
// Request Account
Request(ClientRequestInfo),
// Verified Proof
Verification(Proof),
// Account's userdata is Unallocated
Unallocated,
// Invalid
Invalid,
}
impl Default for AccountState {
fn default() -> Self {
AccountState::Unallocated
}
}
///Errors
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum SpvError {
InvalidBlockHeader,
// blockheader is malformed or out of order
HeaderStoreError,
// header store write/read result is invalid
ParseError,
// other errors with parsing inputs
}
impl error::Error for SpvError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
// temporary measure
None
}
}
impl From<HeaderStoreError> for SpvError {
fn from(e: HeaderStoreError) -> Self {
SpvError::HeaderStoreError
}
}
impl From<DecodeHexError> for SpvError {
fn from(e: DecodeHexError) -> Self {
SpvError::ParseError
}
}
// impl fmt::Debug for SpvError {
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result{
// match self {
// SpvError::InvalidBlockHeader => "BlockHeader is malformed or does not apply ".fmt(f),
// SpvError::HeaderStoreError => "Placeholder headerstore error debug text".fmt(f),
// SpvError::ParseError => "Error parsing blockheaders debug".fmt(f),
// }
// }
// }
impl fmt::Display for SpvError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SpvError::InvalidBlockHeader => "BlockHeader is malformed or does not apply ".fmt(f),
SpvError::HeaderStoreError => "Placeholder headerstore error text".fmt(f),
SpvError::ParseError => "Error parsing blockheaders placceholder text".fmt(f),
}
}
}

View File

@ -0,0 +1,33 @@
use std::{fmt, num::ParseIntError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeHexError {
OddLength,
ParseInt(ParseIntError),
}
impl From<ParseIntError> for DecodeHexError {
fn from(e: ParseIntError) -> Self {
DecodeHexError::ParseInt(e)
}
}
impl fmt::Display for DecodeHexError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
DecodeHexError::OddLength => "input hex string length is odd ".fmt(f),
DecodeHexError::ParseInt(e) => e.fmt(f),
}
}
}
pub fn decode_hex(s: &str) -> Result<Vec<u8>, DecodeHexError> {
if s.len() % 2 != 0 {
Err(DecodeHexError::OddLength)
} else {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| e.into()))
.collect()
}
}

2
programs/btc_spv_bin/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target/
/farf/

View File

@ -0,0 +1,15 @@
[package]
name = "btc_spv_bin"
version = "0.1.0"
authors = ["patrick <patrick.solana.com>"]
edition = "2018"
[dependencies]
reqwest="0.9.19"
clap="2.33.0"
serde_derive="1.0.98"
serde="1.0.99"
[[bin]]
name = "blockheaders"
path = "src/blockheade.rs"

View File

@ -0,0 +1,76 @@
use clap;
use clap::{App, Arg};
use reqwest;
use serde::Deserialize;
// pub type blockHash = [u8; 32];
pub type BlockHeader = [u8; 80];
#[allow(dead_code)]
#[derive(Deserialize)]
struct JsonBH {
hash: String,
ver: u16,
prev_block: String,
mrkl_root: String,
time: u64,
bits: u64,
nonce: u64,
n_tx: u64,
size: u64,
block_index: u64,
main_chain: bool,
height: u64,
received_time: u64,
relayed_by: String,
}
#[allow(dead_code)]
fn get_header_json(hash: &str) -> JsonBH {
let qs = format!("https://www.blockchain.info/rawblock/{}", hash);
let body = reqwest::get(&qs);
match body {
Err(e) => panic!("rest request failed {}", e),
Ok(mut n) => {
if n.status().is_success() {
let jsonbh: JsonBH = n.json().unwrap();
jsonbh
} else {
panic!("request failed");
}
}
}
}
fn get_header_raw(hash: &str) -> String {
let qs = format!("https://blockchain.info/block/{}?format=hex", hash);
let body = reqwest::get(&qs);
match body {
Err(e) => panic!("rest request failed {}", e),
Ok(mut n) => {
if n.status().is_success() {
let textbh: String = n.text().unwrap();
let hs = &textbh[0..160]; // 160 characters since it's in hex format
let header: String = hs.to_string();
header
} else {
panic!("request failed");
}
}
}
}
fn main() {
let matches = App::new("header fetch util")
.arg(Arg::with_name("blockhash"))
.help("block hash to get header from")
.get_matches();
let testhash = "0000000000000bae09a7a393a8acded75aa67e46cb81f7acaa5ad94f9eacd103";
let blockhash = matches.value_of("blockhash").unwrap_or(testhash);
let headerraw = get_header_raw(&blockhash);
println!("header - {}", headerraw);
println!("hash - {}", blockhash);
println!("length - {}", headerraw.len());
// println!("{}", std::str::from_utf8(&header));
}

2
programs/btc_spv_program/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target/
/farf/

View File

@ -0,0 +1,25 @@
[package]
name = "solana-bitcoin-spv-program"
version = "0.19.0-pre0"
description = "Solana Bitcoin spv parsing program"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[dependencies]
bincode = "1.1.4"
chrono = { version = "0.4.0", features = ["serde"] }
log = "0.4.2"
num-derive = "0.2"
num-traits = "0.2"
serde = "1.0.99"
serde_derive = "1.0.98"
solana-sdk = { path = "../../sdk", version = "0.19.0-pre0"}
solana-btc-spv-api = { path = "../btc_spv_api", version = "0.19.0-pre0"}
[lib]
crate-type = ["lib", "cdylib"]
name = "solana_btc_spv_program"

View File

@ -0,0 +1,13 @@
use solana_btc_spv_api::spv_processor::process_instruction;
#[macro_export]
macro_rules! solana_btc_spv_program {
() => {
(
"solana_btc_spv_program".to_string(),
solana_btc_spv_api::id(),
)
};
}
solana_sdk::solana_entrypoint!(process_instruction);