Remove unmaintained btc programs
This commit is contained in:
committed by
mergify[bot]
parent
32fea0496e
commit
6808a04abe
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -250,17 +250,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "btc_spv_bin"
|
|
||||||
version = "1.3.0"
|
|
||||||
dependencies = [
|
|
||||||
"clap",
|
|
||||||
"hex",
|
|
||||||
"reqwest",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.3.0"
|
version = "3.3.0"
|
||||||
@ -3067,20 +3056,6 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "solana-btc-spv-program"
|
|
||||||
version = "1.3.0"
|
|
||||||
dependencies = [
|
|
||||||
"bincode",
|
|
||||||
"hex",
|
|
||||||
"log 0.4.8",
|
|
||||||
"num-derive 0.3.0",
|
|
||||||
"num-traits",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"solana-sdk 1.3.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solana-budget-program"
|
name = "solana-budget-program"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -34,8 +34,6 @@ members = [
|
|||||||
"poh-bench",
|
"poh-bench",
|
||||||
"programs/bpf_loader",
|
"programs/bpf_loader",
|
||||||
"programs/budget",
|
"programs/budget",
|
||||||
"programs/btc_spv",
|
|
||||||
"programs/btc_spv_bin",
|
|
||||||
"programs/config",
|
"programs/config",
|
||||||
"programs/exchange",
|
"programs/exchange",
|
||||||
"programs/failure",
|
"programs/failure",
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "solana-btc-spv-program"
|
|
||||||
version = "1.3.0"
|
|
||||||
description = "Solana Bitcoin spv parsing program"
|
|
||||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
|
||||||
repository = "https://github.com/solana-labs/solana"
|
|
||||||
license = "Apache-2.0"
|
|
||||||
homepage = "https://solana.com/"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bincode = "1.3.1"
|
|
||||||
log = "0.4.2"
|
|
||||||
num-derive = "0.3"
|
|
||||||
num-traits = "0.2"
|
|
||||||
serde = "1.0.112"
|
|
||||||
serde_derive = "1.0.103"
|
|
||||||
solana-sdk = { path = "../../sdk", version = "1.3.0"}
|
|
||||||
hex = "0.4.2"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["lib", "cdylib"]
|
|
||||||
name = "solana_btc_spv_program"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
targets = ["x86_64-unknown-linux-gnu"]
|
|
@ -1,64 +0,0 @@
|
|||||||
## 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 applications
|
|
||||||
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 applications 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 docs section at:
|
|
||||||
https://docs.solana.com/proposals/interchain-transaction-verification
|
|
@ -1,102 +0,0 @@
|
|||||||
#[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(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>,
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
#![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;
|
|
||||||
|
|
||||||
use crate::spv_processor::process_instruction;
|
|
||||||
|
|
||||||
solana_sdk::declare_program!(
|
|
||||||
"BtcSpv1111111111111111111111111111111111111",
|
|
||||||
solana_btc_spv_program,
|
|
||||||
process_instruction
|
|
||||||
);
|
|
@ -1,78 +0,0 @@
|
|||||||
//! 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: BitcoinTransaction,
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
//! 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::*;
|
|
||||||
use log::*;
|
|
||||||
use solana_sdk::account::KeyedAccount;
|
|
||||||
use solana_sdk::instruction::InstructionError;
|
|
||||||
use solana_sdk::program_utils::limited_deserialize;
|
|
||||||
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: &[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: &[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: &[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: &[KeyedAccount],
|
|
||||||
data: &[u8],
|
|
||||||
) -> Result<(), InstructionError> {
|
|
||||||
// solana_logger::setup();
|
|
||||||
|
|
||||||
let command = limited_deserialize::<SpvInstruction>(data)?;
|
|
||||||
|
|
||||||
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 = hex::decode(&testheader)?;
|
|
||||||
let testhashbytes = hex::decode(&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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_transaction_hex() {
|
|
||||||
let testblockhash = "0000000000000bae09a7a393a8acded75aa67e46cb81f7acaa5ad94f9eacd103";
|
|
||||||
let testtxhash = "5b09bbb8d3cb2f8d4edbcf30664419fb7c9deaeeb1f62cb432e7741c80dbe5ba";
|
|
||||||
|
|
||||||
let mut testdatabytes = include_bytes!("testblock.in");
|
|
||||||
let mut headerbytes = hex::encode(&testdatabytes[0..]);
|
|
||||||
let hbc = &headerbytes[0..80];
|
|
||||||
|
|
||||||
let mut txdata = &testdatabytes[80..];
|
|
||||||
|
|
||||||
let vilen = measure_variable_int(&txdata[0..9]).unwrap();
|
|
||||||
let txnum = decode_variable_int(&txdata[0..9]).unwrap();
|
|
||||||
|
|
||||||
txdata = &txdata[vilen..];
|
|
||||||
let tx = BitcoinTransaction::new(txdata.to_vec());
|
|
||||||
|
|
||||||
assert_eq!(tx.inputs.len(), 1);
|
|
||||||
assert_eq!(txnum, 22);
|
|
||||||
assert_eq!(tx.outputs.len(), 1);
|
|
||||||
assert_eq!(tx.version, 1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,457 +0,0 @@
|
|||||||
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 hex::decode(header) {
|
|
||||||
Ok(header) => {
|
|
||||||
let bhbytes = hex::decode(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 BitcoinTransaction {
|
|
||||||
pub inputs: Vec<Input>,
|
|
||||||
|
|
||||||
pub inputs_num: u64,
|
|
||||||
//input utxos
|
|
||||||
pub outputs: Vec<Output>,
|
|
||||||
|
|
||||||
pub outputs_num: u64,
|
|
||||||
//output utxos
|
|
||||||
pub version: u32,
|
|
||||||
//bitcoin network version
|
|
||||||
pub lock_time: u32,
|
|
||||||
|
|
||||||
pub bytes_len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitcoinTransaction {
|
|
||||||
pub fn new(txbytes: Vec<u8>) -> Self {
|
|
||||||
let mut ver: [u8; 4] = [0; 4];
|
|
||||||
ver.copy_from_slice(&txbytes[..4]);
|
|
||||||
let version = u32::from_le_bytes(ver);
|
|
||||||
|
|
||||||
let inputs_num: u64 = decode_variable_int(&txbytes[4..13]).unwrap();
|
|
||||||
let vinlen: usize = measure_variable_int(&txbytes[4..13]).unwrap();
|
|
||||||
let mut inputstart: usize = 4 + vinlen;
|
|
||||||
let mut inputs = Vec::new();
|
|
||||||
|
|
||||||
if inputs_num > 0 {
|
|
||||||
for i in 0..inputs_num {
|
|
||||||
let mut input = Input::new(txbytes[inputstart..].to_vec());
|
|
||||||
inputstart += input.bytes_len;
|
|
||||||
inputs.push(input);
|
|
||||||
}
|
|
||||||
inputs.to_vec();
|
|
||||||
}
|
|
||||||
|
|
||||||
let outputs_num: u64 = decode_variable_int(&txbytes[inputstart..9 + inputstart]).unwrap();
|
|
||||||
let voutlen: usize = measure_variable_int(&txbytes[inputstart..9 + inputstart]).unwrap();
|
|
||||||
|
|
||||||
let mut outputstart: usize = inputstart + voutlen;
|
|
||||||
let mut outputs = Vec::new();
|
|
||||||
for i in 0..outputs_num {
|
|
||||||
let mut output = Output::new(txbytes[outputstart..].to_vec());
|
|
||||||
outputstart += output.bytes_len;
|
|
||||||
outputs.push(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut lt: [u8; 4] = [0; 4];
|
|
||||||
lt.copy_from_slice(&txbytes[outputstart..4 + outputstart]);
|
|
||||||
let lock_time = u32::from_le_bytes(lt);
|
|
||||||
|
|
||||||
assert_eq!(inputs.len(), inputs_num as usize);
|
|
||||||
assert_eq!(outputs.len(), outputs_num as usize);
|
|
||||||
|
|
||||||
BitcoinTransaction {
|
|
||||||
inputs,
|
|
||||||
inputs_num,
|
|
||||||
outputs,
|
|
||||||
outputs_num,
|
|
||||||
version,
|
|
||||||
lock_time,
|
|
||||||
bytes_len: 4 + outputstart,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn hexnew(hex: String) -> Result<BitcoinTransaction, SpvError> {
|
|
||||||
match hex::decode(&hex) {
|
|
||||||
Ok(txbytes) => Ok(BitcoinTransaction::new(txbytes)),
|
|
||||||
Err(e) => Err(SpvError::ParseError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct Input {
|
|
||||||
pub input_type: InputType,
|
|
||||||
// Type of the input
|
|
||||||
pub position: u32,
|
|
||||||
// position of the tx in its Block
|
|
||||||
pub txhash: BitcoinTxHash,
|
|
||||||
// hash of the transaction
|
|
||||||
pub script_length: u64,
|
|
||||||
// length of the spend script
|
|
||||||
pub script: Vec<u8>,
|
|
||||||
// script bytes
|
|
||||||
pub sequence: [u8; 4],
|
|
||||||
// length of the input in bytes
|
|
||||||
pub bytes_len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Input {
|
|
||||||
fn new(ibytes: Vec<u8>) -> Self {
|
|
||||||
let mut txhash: [u8; 32] = [0; 32];
|
|
||||||
txhash.copy_from_slice(&ibytes[..32]);
|
|
||||||
|
|
||||||
let mut tx_out_index: [u8; 4] = [0; 4];
|
|
||||||
tx_out_index.copy_from_slice(&ibytes[32..36]);
|
|
||||||
let position = u32::from_le_bytes(tx_out_index);
|
|
||||||
|
|
||||||
let script_length: u64 = decode_variable_int(&ibytes[36..45]).unwrap();
|
|
||||||
let script_length_len: usize = measure_variable_int(&ibytes[36..45]).unwrap();
|
|
||||||
let script_start = 36 + script_length_len; //checkc for correctness
|
|
||||||
let script_end = script_start + script_length as usize;
|
|
||||||
let input_end = script_end + 4;
|
|
||||||
|
|
||||||
let script: Vec<u8> = ibytes[script_start..script_length as usize].to_vec();
|
|
||||||
|
|
||||||
let mut sequence: [u8; 4] = [0; 4];
|
|
||||||
sequence.copy_from_slice(&ibytes[script_end..input_end]);
|
|
||||||
|
|
||||||
let input_type: InputType = InputType::NONE; // testing measure
|
|
||||||
|
|
||||||
Self {
|
|
||||||
input_type,
|
|
||||||
position,
|
|
||||||
txhash,
|
|
||||||
script_length,
|
|
||||||
script,
|
|
||||||
sequence,
|
|
||||||
bytes_len: input_end,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default() -> Self {
|
|
||||||
let txh: [u8; 32] = [0; 32];
|
|
||||||
let seq: [u8; 4] = [0; 4];
|
|
||||||
|
|
||||||
Self {
|
|
||||||
input_type: InputType::NONE,
|
|
||||||
position: 55,
|
|
||||||
txhash: txh,
|
|
||||||
script_length: 45,
|
|
||||||
script: txh.to_vec(),
|
|
||||||
sequence: seq,
|
|
||||||
bytes_len: 123,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub enum InputType {
|
|
||||||
LEGACY,
|
|
||||||
COMPATIBILITY,
|
|
||||||
WITNESS,
|
|
||||||
NONE,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct Output {
|
|
||||||
pub output_type: OutputType,
|
|
||||||
// type of the output
|
|
||||||
pub value: u64,
|
|
||||||
// amount of btc in sats
|
|
||||||
pub script: Vec<u8>,
|
|
||||||
|
|
||||||
pub script_length: u64,
|
|
||||||
|
|
||||||
pub bytes_len: usize,
|
|
||||||
// payload: Option<Vec<u8>>,
|
|
||||||
// // data sent with the transaction (Op return)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Output {
|
|
||||||
fn new(obytes: Vec<u8>) -> Self {
|
|
||||||
let mut val: [u8; 8] = [0; 8];
|
|
||||||
val.copy_from_slice(&obytes[..8]);
|
|
||||||
let value: u64 = u64::from_le_bytes(val);
|
|
||||||
|
|
||||||
let script_start: usize = 8 + measure_variable_int(&obytes[8..17]).unwrap();
|
|
||||||
let script_length = decode_variable_int(&obytes[8..script_start]).unwrap();
|
|
||||||
let script_end: usize = script_start + script_length as usize;
|
|
||||||
|
|
||||||
let script = obytes[script_start..script_end].to_vec();
|
|
||||||
|
|
||||||
let output_type = OutputType::WPKH; // temporary hardcode
|
|
||||||
|
|
||||||
Self {
|
|
||||||
output_type,
|
|
||||||
value,
|
|
||||||
script,
|
|
||||||
script_length,
|
|
||||||
bytes_len: script_end,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default() -> Self {
|
|
||||||
let transaction_hash: [u8; 32] = [0; 32];
|
|
||||||
|
|
||||||
Self {
|
|
||||||
output_type: OutputType::WPKH,
|
|
||||||
value: 55,
|
|
||||||
script: transaction_hash.to_vec(),
|
|
||||||
script_length: 45,
|
|
||||||
bytes_len: 123,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub enum OutputType {
|
|
||||||
WPKH,
|
|
||||||
WSH,
|
|
||||||
OP_RETURN,
|
|
||||||
PKH,
|
|
||||||
SH,
|
|
||||||
NONSTANDARD,
|
|
||||||
// https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
|
|
||||||
}
|
|
||||||
|
|
||||||
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: BitcoinTransaction,
|
|
||||||
// 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 holds a HeaderStore structure
|
|
||||||
Headers(HeaderAccountInfo),
|
|
||||||
// Account's data is Unallocated
|
|
||||||
Unallocated,
|
|
||||||
// Invalid
|
|
||||||
Invalid,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AccountState {
|
|
||||||
fn default() -> Self {
|
|
||||||
AccountState::Unallocated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///Errors
|
|
||||||
#[derive(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
|
|
||||||
InvalidAccount,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 From<hex::FromHexError> for SpvError {
|
|
||||||
fn from(e: hex::FromHexError) -> 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),
|
|
||||||
SpvError::InvalidAccount => "Provided account is not usable or does not exist".fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
@ -1,126 +0,0 @@
|
|||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use std::{fmt, num::ParseIntError};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum DecodeHexError {
|
|
||||||
InvalidLength(LengthError),
|
|
||||||
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::InvalidLength(LengthError::OddLength) => {
|
|
||||||
"input hex string length is odd ".fmt(f)
|
|
||||||
}
|
|
||||||
DecodeHexError::InvalidLength(LengthError::Maximum(e)) => {
|
|
||||||
"input exceeds the maximum length".fmt(f)
|
|
||||||
}
|
|
||||||
DecodeHexError::InvalidLength(LengthError::Minimum(e)) => {
|
|
||||||
"input does not meet the minimum length".fmt(f)
|
|
||||||
}
|
|
||||||
DecodeHexError::ParseInt(e) => e.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub enum LengthError {
|
|
||||||
OddLength,
|
|
||||||
Maximum(u32),
|
|
||||||
Minimum(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode_hex(s: &str) -> Result<Vec<u8>, DecodeHexError> {
|
|
||||||
if s.len() % 2 != 0 {
|
|
||||||
Err(DecodeHexError::InvalidLength(LengthError::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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn measure_variable_int(vint: &[u8]) -> Result<usize, DecodeHexError> {
|
|
||||||
let ln = vint.len();
|
|
||||||
if ln > 9 {
|
|
||||||
return Err(DecodeHexError::InvalidLength(LengthError::Maximum(9)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let val: usize = match vint[0] {
|
|
||||||
0..=252 => 1,
|
|
||||||
253 => 3,
|
|
||||||
254 => 5,
|
|
||||||
255 => 9,
|
|
||||||
};
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode_variable_int(vint: &[u8]) -> Result<u64, DecodeHexError> {
|
|
||||||
let ln = vint.len();
|
|
||||||
if ln > 9 {
|
|
||||||
return Err(DecodeHexError::InvalidLength(LengthError::Maximum(9)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let val: u64 = match vint[0] {
|
|
||||||
0..=252 => u64::from(vint[0]),
|
|
||||||
253 => {
|
|
||||||
let mut val: [u8; 2] = [0; 2];
|
|
||||||
val.copy_from_slice(&vint[1..3]);
|
|
||||||
u64::from(u16::from_le_bytes(val))
|
|
||||||
}
|
|
||||||
254 => {
|
|
||||||
let mut val: [u8; 4] = [0; 4];
|
|
||||||
val.copy_from_slice(&vint[1..5]);
|
|
||||||
u64::from(u32::from_le_bytes(val))
|
|
||||||
}
|
|
||||||
255 => {
|
|
||||||
let mut val: [u8; 8] = [0; 8];
|
|
||||||
val.copy_from_slice(&vint[1..9]);
|
|
||||||
u64::from_le_bytes(val)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::utils::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_variable_int() {
|
|
||||||
let var_int_a = hex::decode("6a32a4").unwrap();
|
|
||||||
let var_int_b = hex::decode("fd26021d32").unwrap();
|
|
||||||
let var_int_c = hex::decode("fe703a0f00").unwrap();
|
|
||||||
|
|
||||||
let value_a = decode_variable_int(&var_int_a[0..]).unwrap();
|
|
||||||
let value_b = decode_variable_int(&var_int_b[0..]).unwrap();
|
|
||||||
let value_c = decode_variable_int(&var_int_c[0..]).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(106, value_a);
|
|
||||||
assert_eq!(550, value_b);
|
|
||||||
assert_eq!(998_000, value_c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_measure_variable_int() {
|
|
||||||
let var_int_a = hex::decode("6a32a4").unwrap();
|
|
||||||
let var_int_b = hex::decode("fd26021d32").unwrap();
|
|
||||||
let var_int_c = hex::decode("fe703a0f00").unwrap();
|
|
||||||
|
|
||||||
let len_a = measure_variable_int(&var_int_a[0..]).unwrap();
|
|
||||||
let len_b = measure_variable_int(&var_int_b[0..]).unwrap();
|
|
||||||
let len_c = measure_variable_int(&var_int_c[0..]).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(len_a, 1);
|
|
||||||
assert_eq!(len_b, 3);
|
|
||||||
assert_eq!(len_c, 5);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "btc_spv_bin"
|
|
||||||
version = "1.3.0"
|
|
||||||
description = "Solana Bitcoin spv parsing program"
|
|
||||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
|
||||||
repository = "https://github.com/solana-labs/solana"
|
|
||||||
license = "Apache-2.0"
|
|
||||||
homepage = "https://solana.com/"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clap="2.33.1"
|
|
||||||
reqwest = { version = "0.10.6", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
|
||||||
serde="1.0.112"
|
|
||||||
serde_derive="1.0.103"
|
|
||||||
hex = "0.4.2"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "blockheaders"
|
|
||||||
path = "src/blockheade.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "blocks"
|
|
||||||
path = "src/block.rs"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
targets = ["x86_64-unknown-linux-gnu"]
|
|
@ -1,48 +0,0 @@
|
|||||||
use clap::{App, Arg};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
|
|
||||||
fn get_block_raw(hash: &str) -> String {
|
|
||||||
let qs = format!("https://blockchain.info/block/{}?format=hex", hash);
|
|
||||||
let body = reqwest::blocking::get(&qs);
|
|
||||||
match body {
|
|
||||||
Err(e) => panic!("rest request failed {}", e),
|
|
||||||
Ok(n) => {
|
|
||||||
if n.status().is_success() {
|
|
||||||
n.text().unwrap()
|
|
||||||
} else {
|
|
||||||
panic!("request failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_file(fname: String, bytes: &[u8]) -> std::io::Result<()> {
|
|
||||||
let mut buffer = File::create(fname)?;
|
|
||||||
buffer.write_all(bytes)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let matches = App::new("header fetch util")
|
|
||||||
.arg(Arg::with_name("blockhash"))
|
|
||||||
.arg(Arg::with_name("output"))
|
|
||||||
.help("block hash to get header from")
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
let default_output = "file";
|
|
||||||
let output = matches.value_of("output").unwrap_or(default_output);
|
|
||||||
|
|
||||||
let testhash = "0000000000000bae09a7a393a8acded75aa67e46cb81f7acaa5ad94f9eacd103";
|
|
||||||
let blockhash = matches.value_of("blockhash").unwrap_or(testhash);
|
|
||||||
let blockraw = get_block_raw(&blockhash);
|
|
||||||
|
|
||||||
if default_output == output {
|
|
||||||
let fname = format!("block-{}.in", blockhash);
|
|
||||||
let outf = hex::decode(&blockraw).unwrap();
|
|
||||||
let arr = &outf[0..];
|
|
||||||
write_file(fname, arr).unwrap();
|
|
||||||
} else {
|
|
||||||
println!("{}", blockraw);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
use clap::{App, Arg};
|
|
||||||
use serde_derive::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::blocking::get(&qs);
|
|
||||||
match body {
|
|
||||||
Err(e) => panic!("rest request failed {}", e),
|
|
||||||
Ok(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::blocking::get(&qs);
|
|
||||||
match body {
|
|
||||||
Err(e) => panic!("rest request failed {}", e),
|
|
||||||
Ok(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));
|
|
||||||
}
|
|
Reference in New Issue
Block a user