【Rust】APIでBTC, ETHのUSD価格を取得する

use reqwest::Client;
use serde::{Serialize, Deserialize};
use serde_json::{Value};

#[tokio::main]
async fn main() {

    let btc: String = "BTC".to_string();
    let btc_price: Value = get_price(btc).await.unwrap();
    println!("{}", btc_price);

    let eth: String = "ETH".to_string();
    let eth_price: Value = get_price(eth).await.unwrap();
    println!("{}", eth_price);
}

async fn get_price(code: String) -> Result<Value, Box<dyn std::error::Error>> {
    let mut url:String = "https://api.coinbase.com/v2/exchange-rates?currency=".to_string();
    url.push_str(&code);
    let contents = reqwest::get(url).await?.text().await?;
    let res: Value = serde_json::from_str(&contents).unwrap();
    println!("{:?}", res["data"]["rates"]["USD"]);
    Ok((res["data"]["rates"]["USD"]).clone())
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.97s
Running `target/debug/wallet`
String(“94359.295”)
“94359.295”
String(“3246.82”)
“3246.82”

処理速度が遅いが、取得はできる。
よし、さて、PoSでもやるか!

【Rust】APIでbitcoinの価格を取得したい

Pythonで書くとこんな感じ

import requests

def get_bitcoin_price():
    url = 'https://coincheck.com/api/ticker'
    response = requests.get(url)
    data = response.json()
    last_price = data['last']
    return last_price

bitcoin_price = get_bitcoin_price()
print(f"現在のbtc価格: {bitcoin_price} JPY")

これをRustで書く。構造体にしてあげる。

use reqwest::Client;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Clone, Debug)]
struct Price {
    last: f32,
    bid: f32,
    ask: f32,
    high: f32,
    volume: f32,
    timestamp: u32
} 

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://coincheck.com/api/ticker";
    let contents = reqwest::get(url).await?.text().await?;
    println!("{:?}", &contents);
    let res: Price = serde_json::from_str(&contents).unwrap();
    println!("{:?}", res.last);
    Ok(())
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
Running `target/debug/wallet`
“{\”last\”:14905296.0,\”bid\”:14904987.0,\”ask\”:14907006.0,\”high\”:15136729.0,\”low\”:14578786.0,\”volume\”:1207.84604682,\”timestamp\”:1736567711}”
14905296.0

coincheckは日本円なので、USD変換のAPIの方が良いな。かつ、Ethの値も一緒に取れると尚良

use reqwest::Client;
use serde::{Serialize, Deserialize};
use serde_json::{Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://api.coinbase.com/v2/exchange-rates?currency=BTC";
    let contents = reqwest::get(url).await?.text().await?;
    // println!("{:?}", &contents);
    let res: Value = serde_json::from_str(&contents).unwrap();
    println!("{:?}", res["data"]["rates"]["USD"]);
    Ok(())
}

coinbaseのapiだと、btcもethもusdベースで取得できる。よっしゃああああああああああああ

【Rust】p256でhexにencodeしたpublic keyをdecodeしてverify

hex::decode(&public_key_hex) でdecodeして、さらにfrom_sec1_bytesでpublic keyにします。

    let public_key = secret_key.public_key();
    let public_key_serialized = public_key.to_string();
    println!("Public Key: \n{}", public_key_serialized);

    let public_key_hex = hex::encode(&public_key.to_sec1_bytes());
    println!("hex Public Key: \n{}", public_key_hex);

    let public_key_decode = hex::decode(&public_key_hex).unwrap();
    println!("decode Public Key: \n{:?}", &public_key_decode);

    let public_key = p256::PublicKey::from_sec1_bytes(&public_key_decode).expect("import error");

    println!("decode Public Key: \n{:?}", &public_key);

    let signing_key: SigningKey = secret_key.into();
    let message = b"ECDSA proves knowledge of a secret number in the context of a single message";
    let signature: Signature = signing_key.sign(message);

    let verifying_key: VerifyingKey = public_key.into();
    assert!(verifying_key.verify(message, &signature).is_ok());

$ cargo run
// 省略
hex Public Key:
040b807b5e5fee8c91df858e09aa6dc085eae1b9b70783ce32ec68ad4994f9d16a991e277a1515a22e35d2d7935dec95946f31a2e3ea1e125a9067a177523063b0
decode Public Key:
[4, 11, 128, 123, 94, 95, 238, 140, 145, 223, 133, 142, 9, 170, 109, 192, 133, 234, 225, 185, 183, 7, 131, 206, 50, 236, 104, 173, 73, 148, 249, 209, 106, 153, 30, 39, 122, 21, 21, 162, 46, 53, 210, 215, 147, 93, 236, 149, 148, 111, 49, 162, 227, 234, 30, 18, 90, 144, 103, 161, 119, 82, 48, 99, 176]
decode Public Key:
PublicKey { point: AffinePoint { x: FieldElement(0x3B226D0B94EFABC8980328AB7FE8AE347D418871A770F1EFFE8A24FCAC9EE9DB), y: FieldElement(0x307BB7BAC4B807E9D59F65BEC20EC8AB54591BE07549140238927D01CA9152EF), infinity: 0 } }

おおお、このdecodeと署名の確認は本当に上手くいくのか再度トランザクションを実際に作ってテストする必要がありますね。

### signatureとpublic keyをPostされた側でのverify

    let signed_ut1 = SignedTransaction {time: unsignedtransaction.time, sender: unsignedtransaction.sender, receiver: unsignedtransaction.receiver, amount: unsignedtransaction.amount, signature: sig1.to_string()};
    println!("{:?}", signed_ut1);

    let public_key_decode = hex::decode(&signed_ut1.sender).unwrap();
    let public_key = p256::PublicKey::from_sec1_bytes(&public_key_decode).expect("import error");
    let verifying_key: VerifyingKey = public_key.into();

    let signature: Signature = signed_ut1.signature.parse::<Signature>().unwrap();

    let posted_transaction = UnsignedTransaction { time:signed_ut1.time, sender:signed_ut1.sender, receiver: signed_ut1.receiver, amount: signed_ut1.amount};
    let posted_serialized: String = serde_json::to_string(&posted_transaction).unwrap();

    assert!(verifying_key.verify(posted_serialized.as_bytes(), &signature).is_ok());

おおおおおおお、hexのpublic keyからちゃんと署名の検証ができてます。

【Rust】別のノードのブロックサイズが大きければ更新

自分が持っているblockのサイズと別のノードが持っているブロックサイズを比較して、相手の方が大きければ自分のブロックチェーンを更新する

    let path = "own_blocks.txt";
    let own_blocks = count_lines(path);
    println!("{}", own_blocks);

    let json_data = json();
    let deserialized = json!(**&json_data);
    let objs: Vec<Block> = serde_json::from_value(deserialized).unwrap();
    if objs.len() > own_blocks {
        let mut file = File::create("data/blocks.txt").expect("file not found.");
        for obj in &objs {
            let t = serde_json::to_string(&obj).unwrap();
            writeln!(file, "{}", t).expect("can not write.");
        }
    }
    println!("{}", objs.len());

OK、これを関数化して繋げる。

【Rust】ファイルの行数をカウント

fn count_lines(path: &str) -> usize{
    let file = File::open(path).unwrap();
    let br = BufReader::new(file);
    let mut counter = 0;
    br.lines().for_each(|_| counter += 1);
    counter
}

これで、自分が持っているblockの数を数えられるようになります。

    let path = "own_blocks.txt";
    let own_blocks = count_lines(path);
    println!("{}", own_blocks);

【Rust】structのjsonシリアライズ・デシリアライズ(修正版)

jsonにシリアライズする際に、serde_json::to_string(&names).unwrap();としてしまうと、String型になってしまうため、デシリアライズの方法がわからなくなる。そのため、vectorにstructを入れたら、to_stringとせずにそのままjson化する。

use axum::{routing::get, Json, Router};
use serde_json::{json, Value};
use serde::{Serialize, Deserialize};
use std::io::BufReader;

#[derive(Serialize, Deserialize, Debug)]
struct Name {
    family: String,
    first: String,
    age: u32,
}

fn main() {
    let json_data = json();
    println!("{:?}", &json_data);
    let deserialized = json!(**&json_data);
    println!("{:?}", &deserialized);
    let objs: Vec<Name> = serde_json::from_value(deserialized).unwrap();
    println!("{:?}", objs);
    for obj in &objs {
        println!("{:?}", &obj);
    }
}

fn json() -> Json<Value> {
    let n1 = Name { family: "Yamada".to_string(), first: "Taro".to_string(), age: 20 };
    let n2 = Name { family: "Tanaka".to_string(), first: "Hanako".to_string(), age: 18 };
    let mut names: Vec<Name> = Vec::new();
    names.push(n1);
    names.push(n2);
    Json(json!(names))
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
Running `target/debug/axum`
Json(Array [Object {“age”: Number(20), “family”: String(“Yamada”), “first”: String(“Taro”)}, Object {“age”: Number(18), “family”: String(“Tanaka”), “first”: String(“Hanako”)}])
Array [Object {“age”: Number(20), “family”: String(“Yamada”), “first”: String(“Taro”)}, Object {“age”: Number(18), “family”: String(“Tanaka”), “first”: String(“Hanako”)}]
[Name { family: “Yamada”, first: “Taro”, age: 20 }, Name { family: “Tanaka”, first: “Hanako”, age: 18 }]
Name { family: “Yamada”, first: “Taro”, age: 20 }
Name { family: “Tanaka”, first: “Hanako”, age: 18 }

【Rust】サーバが保持しているチェーンを返すメソッドを実装したい

サーバが保持しているブロックチェーンを返すメソッド

### axumでjsonを返す方法について

use axum::{routing::get, Json, Router};
use serde_json::{json, Value};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(||async {"Hello, World!"}))
            .route("/json", get(json));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn json() -> Json<Value> {
    Json(json!({"data": 42}))
}

### Struct(構造体)をjsonにしてAPIにする方法

use axum::{routing::get, Json, Router};
use serde_json::{json, Value};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Name {
    family: String,
    first: String,
    age: u32,
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(||async {"Hello, World!"}))
            .route("/json", get(json));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn json() -> Json<Value> {
    let mut names: Vec<Name> = Vec::new();
    let n1 = Name { family: "Yamada".to_string(), first: "Taro".to_string(), age: 20 };
    let n2 = Name { family: "Tanaka".to_string(), first: "Hanako".to_string(), age: 18 };
    names.push(n1);
    names.push(n2);
    let data = serde_json::to_string(&names).unwrap();
    Json(json!(data))
}

“[{\”family\”:\”Yamada\”,\”first\”:\”Taro\”,\”age\”:20},{\”family\”:\”Tanaka\”,\”first\”:\”Hanako\”,\”age\”:18}]”

ここまではOKなんだけど、問題は、これをどうやって読み取ってNameごとに1行ずつ保存するかだな。。

【Rust】マイニングの難易度自動調整の仕組みを実装する

– PoWの難易度の自動調整を追加する
 L originの難易度があり、マイニングの経過した時間により難易度を上げたり下げたりする

5秒以上かかったらdifficultyを下げ、1秒以下ならdifficultyを上げる。

use serde::{Serialize, Deserialize};
use chrono::{Utc, Local, DateTime, Date};
use sha2::{Digest, Sha256};
use rand::Rng;
use std::{thread, time};
use floating_duration::TimeAsFloat;

#[derive(Debug, Serialize, Clone, Deserialize)]
struct Block {
    time: String,
    transactions: Vec<String>,
    hash: String,
    nonce: String,
}

unsafe fn pow() {
    let now = time::Instant::now();
    let previous_hash = "b9b9ee9ffc95fa4956b63b6043a99d0a8f04e0e52e687fc1958d3c6dff885f01";
    let mut num = rand::thread_rng().gen_range(0..1000000);
    let mut hash_num = format!("{}{}", previous_hash, num.to_string());
    let mut header = Sha256::digest(hash_num);
    let mut target: String  = (&hex::encode(header)[..4]).to_string();
    
    let mut cnt = 1;
    println!("count: {} {:x}", cnt, header);

    let str: String = "0".to_string();
    let difficulty_str = str.repeat(difficulty.try_into().unwrap());
    let n:usize = difficulty as usize;

    while target != difficulty_str {
        println!("count: {} {:x}", cnt, header);
        num = rand::thread_rng().gen_range(0..1000000);
        hash_num = format!("{}{}", previous_hash, num.to_string());
        header = Sha256::digest(hash_num);
        target = (&hex::encode(header)[..n]).to_string();
        cnt += 1;
    }
    println!("count: {} {:x}", cnt, header);
    println!("{:?}", now.elapsed());

    let t = vec!["sender".to_string(), "receiver".to_string(), "amount".to_string()];
    let utc_datetime: DateTime<Utc> = Utc::now();
    let b = Block{time:utc_datetime.to_string(), transactions: t, hash: hex::encode(header).to_string(), nonce:num.to_string()};
    println!("{:?}", b);
    println!("difficulty is {}", difficulty);
    if now.elapsed().as_fractional_secs() >= 5.0 {
        difficulty -= 1;
    } else if now.elapsed().as_fractional_secs() < 1.0 {
        difficulty += 1;
    }
}

static mut difficulty: u32 = 4;

fn main(){
    unsafe {pow()};       
    unsafe {pow()};   
    unsafe {pow()};        
}

$ cargo run

count: 393498 5c3b6663fe657641be5f6732ab2762fcc762aa9ffa598d6ee94dad59a4c4b9ca
count: 393499 000000215ec2fa2c01340818563a8397e50514204dcdca7f0addeeb7ff053e95
9.333024984s
Block { time: “2025-01-09 22:49:40.884851370 UTC”, transactions: [“sender”, “receiver”, “amount”], hash: “000000215ec2fa2c01340818563a8397e50514204dcdca7f0addeeb7ff053e95”, nonce: “175647” }
difficulty is 5

最初、difficultyが4だったのが、difficultyが5に変化しているのがわかります。
unsafeはあまり使いたくないですね。

【Rust】複数ファイル使用時にmain.rsのstructを利用する

main.rsで定義したstructを複数ファイル使用時にサブのrsで利用したい場合

mod sub;

#[derive(Debug)]
struct Name {
    family: String,
    first: String,
    age: i32,
}

fn main(){
    sub::print_name();
}

use crate::*;と書く。

sub.rs

use crate::Name;

pub fn print_name() {
    let n = Name { family: "Yamada".to_string(), first: "Taro".to_string(), age: 20 };
    println!("{:?}", n);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/sample`
Name { family: “Yamada”, first: “Taro”, age: 20 }

なるほど〜、crateっていう表現になるのね…

【Rust】UTXOのロジックで残高照会の仕組みを実装したい

ロジック
1. blockからトランザクションデータを抽出する
2. トランザクションデータのsendに自分のアドレスがあればamountからマイナス
3. トランザクションデータのreceiverに自分のアドレスがあればamountをプラス
4. 2・3をループで回して、合計値をウォレットのGUIで表示

// gui表示
async fn handle_balance()-> axum::response::Html<String> {

    let balance = balance().unwrap();

    let tera = tera::Tera::new("templates/*").unwrap();

    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    context.insert("balance", &balance);

    let output = tera.render("balance.html", &context);
    axum::response::Html(output.unwrap())
}

// balance計算
fn balance()-> Result<i32, Box<dyn std::error::Error>>{
    // アドレスの読み込み
    let contents = std::fs::read_to_string("secret.pem")
        .expect("something went wrong reading the file");
    let secret_pem = contents.trim();

    let secret_key = secret_pem.parse::<SecretKey>().unwrap();
    let private_key_serialized = hex::encode(&secret_key.to_bytes());

    let public_key = secret_key.public_key();
    let address = new_address(&public_key); 

    let mut balance: i32 = 0;
    for result in BufReader::new(File::open("blocks.txt")?).lines() {
        let l = result?;
        let param:Block = serde_json::from_str(&l).unwrap();
        for transaction in param.transactions {
            if transaction.receiver == address {
                balance += transaction.amount;
            } else if transaction.sender == address{
                balance -= transaction.amount; 
            }
        }
    }
    println!("balance is {}", balance);
    return Ok(balance);
}

うん、これは特に問題ないですね。