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:
parent
8362b408d9
commit
2b696ac8dc
|
@ -291,6 +291,16 @@ dependencies = [
|
||||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "build_const"
|
name = "build_const"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -3127,6 +3137,34 @@ dependencies = [
|
||||||
"solana-sdk 0.19.0-pre0",
|
"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]]
|
[[package]]
|
||||||
name = "solana-bpf-loader-api"
|
name = "solana-bpf-loader-api"
|
||||||
version = "0.19.0-pre0"
|
version = "0.19.0-pre0"
|
||||||
|
|
|
@ -26,6 +26,9 @@ members = [
|
||||||
"programs/bpf_loader_program",
|
"programs/bpf_loader_program",
|
||||||
"programs/budget_api",
|
"programs/budget_api",
|
||||||
"programs/budget_program",
|
"programs/budget_program",
|
||||||
|
"programs/btc_spv_program",
|
||||||
|
"programs/btc_spv_api",
|
||||||
|
"programs/btc_spv_bin",
|
||||||
"programs/config_api",
|
"programs/config_api",
|
||||||
"programs/config_program",
|
"programs/config_program",
|
||||||
"programs/config_tests",
|
"programs/config_tests",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target/
|
||||||
|
/farf/
|
|
@ -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"
|
|
@ -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 block’s 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
|
|
@ -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>,
|
||||||
|
}
|
|
@ -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"
|
||||||
|
);
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 block’s 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target/
|
||||||
|
/farf/
|
|
@ -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"
|
|
@ -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));
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target/
|
||||||
|
/farf/
|
|
@ -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"
|
|
@ -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);
|
Loading…
Reference in New Issue