Remove unmaintained btc programs
This commit is contained in:
parent
32fea0496e
commit
6808a04abe
|
@ -250,17 +250,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "btc_spv_bin"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"hex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.3.0"
|
||||
|
@ -3067,20 +3056,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "solana-budget-program"
|
||||
version = "1.3.0"
|
||||
|
|
|
@ -34,8 +34,6 @@ members = [
|
|||
"poh-bench",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/btc_spv",
|
||||
"programs/btc_spv_bin",
|
||||
"programs/config",
|
||||
"programs/exchange",
|
||||
"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));
|
||||
}
|
Loading…
Reference in New Issue