【Rust】構造体でvalueから余計な項目(Null値)を取りたくないときは?

取得して処理する元の値(json)
Object {“currencySymbol”: Null, “id”: String(“likecoin”), “rateUsd”: String(“0.0009301600000000”), “symbol”: String(“LIKE”), “type”: String(“crypto”)}, Object {“currencySymbol”: String(“£”), “id”: String(“falkland-islands-pound”), “rateUsd”: String(“1.2207493203478160”), “symbol”: String(“FKP”), “type”: String(“fiat”)},….

構造体

#[derive(Debug, Serialize, Clone, Deserialize)]
struct Rate {
    id: String,
    symbol: String,
    currencySymbol: String,
    r#type: String,
    rateUsd: String,
}

これで、serde_json::from_valueで構造体Rateに入れようとすると

    let mut coins = get_price().await.unwrap();

    let objs: Vec<Rate> = serde_json::from_value(coins).unwrap();
    for obj in objs {
        println!("{:?}", &obj);
    }    

thread ‘main’ panicked at src/main.rs:17:57:
called `Result::unwrap()` on an `Err` value: Error(“invalid type: null, expected a string”, line: 0, column: 0)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

構造体ではcurrencySymbolをStringで定義しているが、一部の値でNullが含まれているためエラーになってしまう…

Nullを空文字に置換できないか、構造体でString or Nullみたいな書き方はできないか、 など色々試行錯誤したが、いっそのこと

#[derive(Debug, Serialize, Clone, Deserialize)]
struct Rate {
    id: String,
    symbol: String,
    // currencySymbol: String,
    r#type: String,
    rateUsd: String,
}

currencySymbol自体を取らないとしたら、
$ cargo run

Rate { id: “salvadoran-colón”, symbol: “SVC”, type: “fiat”, rateUsd: “0.1137841148955581” }
Rate { id: “saint-helena-pound”, symbol: “SHP”, type: “fiat”, rateUsd: “1.2207493203478160” }
Rate { id: “haitian-gourde”, symbol: “HTG”, type: “fiat”, rateUsd: “0.0076549269176472” }
Rate { id: “bermudan-dollar”, symbol: “BMD”, type: “fiat”, rateUsd: “1.0000000000000000” }
Rate { id: “turkmenistani-manat”, symbol: “TMT”, type: “fiat”, rateUsd: “0.2857142857142857” }
Rate { id: “litecoin”, symbol: “LTC”, type: “crypto”, rateUsd: “104.0104020958773373” }

あれ、できるやん。つまり、元データから構造体で定義したデータだけ引っこ抜くということが可能だとわかった。
元データと一致していないといけないと思ったんだが、、、 なんのこっちゃ

PoS(Proof of Stake)と DPoS(Delegated Proof of Stake)、NPosの違いは

PoS
トークンの保有量に応じて承認権が与えられる仕組みです。暗号資産を多く保有する承認者に権利が集中しやすいという課題があります。

DPoS
PoSの発展系で、トークンの保有量に応じて投票権が割り当てられ、投票によって取引の承認を委任する仕組みです。保有量と委任された票の合計で承認者が選ばれるため、PoSと比べて民主主義的な仕組みといえます。また、取引承認に必要な承認数を減らすことができるため、高速なトランザクション処理を実現できます。

NPos
NPoSではValidatorとNominatorのStakeの合計が多い上位50〜1,000人がValidatorプールとして選ばれます。ValidatorとNominatorをValidator選別のスキームに取り入れることによって、ネットワーク全体のセキュリティを担保することが可能になります。この仕組みにより単体の大量DOT保持者へのシステムの依存を回避することができ、DOT保持者全員が参加できることにより悪意を持つユーザーがValidatorになることを困難にします(Nominatorに選ばれるには信頼を築く必要があるため)。

PoSだと、保有量が多い方が有利なアルゴリズムだが、Nominated(NPoS)の場合はランダムに選別するので、より公平性が保たれる仕組みですね。

【Rust】Proof of StakeをRustで書きたい

use std::collections::HashMap;
use chrono::{Utc, Local, DateTime, Date};
use sha2::Sha256;
use sha2::Digest;
use rand::Rng;
use std::io;
use std::io::Error;

#[derive(Debug)]
struct Block {
    index: u32,
    previous_hash: String,
    timestamp: DateTime<Utc>,
    data: Vec<String>,
    validator: String,
    hash: String,
}

#[derive(Debug)]
struct BlockChain {
    chain: Vec<Block>,
    unconfirmed_data: Vec<String>,
    validators: HashMap<String, i32>,
    staked_tokens: Vec<i32>,
}

impl BlockChain {

    const minimum_stake: i32 = 10;

    fn last_block(&self) -> &Block {
        return self.chain.last().unwrap();
    }

    fn add_data(&mut self, new_data:String) {
        self.unconfirmed_data.push(new_data);
    }

    fn add_validator(&mut self, validator: String, stake: i32) {
        if stake >= Self::minimum_stake {
            self.validators.insert(validator, stake);
            self.staked_tokens.push(stake);
        } else {
            println!("{} does not meet the minimum stake requirement.", validator);
        }
    }

    fn select_validator(self) -> String {
        let total_stake: i32 = self.staked_tokens.iter().sum();
        let mut selected_validator: String = "".to_string();
        let mut rnd = rand::thread_rng();
        while selected_validator == "".to_string() {
            let pick = rnd.gen_range(0..total_stake);
            let mut current: i32 = 0;
            for (validator, stake) in &self.validators {
                // println!("{} {}", validator, stake);
                current += stake;
                if current > pick{
                    selected_validator = validator.to_string();
                    break;
                }
            }
        }
        return selected_validator;
    }

    fn create_block(mut self, validator: String)-> Result<(), String> {
        if self.unconfirmed_data.is_empty() {
            return Err("No transaction".to_string());
        }
        let last_block = self.last_block(); 
        let new_block = Block {
            index: last_block.index + 1,
            previous_hash: last_block.hash.clone(),
            timestamp: Utc::now(),
            data : self.unconfirmed_data.clone(),
            validator: validator,
            hash: "".to_string() 
        };
        self.chain.push(new_block);
        self.unconfirmed_data.clear();
        Ok(())
    }

    fn display_chain(self) {
        println!("{:?}", self.chain);
    }

}

fn calculate_hash(data: String) -> String {
    let mut hasher = Sha256::new();
    hasher.update(data);
    let hashed_sha256 = hasher.finalize();
    return hex::encode(hashed_sha256);
}

fn main() {
    let genesis_hash = calculate_hash("Genesis Block".to_string());
    let genesis_block = Block { index: 0, previous_hash: "".to_string(), timestamp: Utc::now(), data: vec!["Genesis Block".to_string()], validator: "".to_string(), hash: genesis_hash };

    let mut blockchain = BlockChain { chain: vec![genesis_block], unconfirmed_data: vec![], validators: HashMap::from([]), staked_tokens: vec![]};

    blockchain.add_validator("Alice".to_string(), 200);
    blockchain.add_validator("Bob".to_string(), 300);
    blockchain.add_validator("Chan".to_string(), 400);

    blockchain.add_data("James got 1000 token".to_string());
    blockchain.add_data("James sent 500 token to Jhon".to_string());

    let selected_validator = blockchain.select_validator();
    println!("Selected validator is {}", selected_validator);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/sample`
Selected validator is Bob

基本的にPythonで書くのとそこまで変わりませんね。

【Rust】structの中でhashmapを使用したい

まずはhashmapから

use std::collections::HashMap;

fn main(){
    let mut map = HashMap::new();
    map.insert("x", 10);
    map.insert("y", 20);
    map.insert("z", 30);
    
    for (k, v) in &map {
        println!("{} {}", k, v);
    }

}

これを構造体で使いたい

#[derive(Debug)]
struct Name {
    name: HashMap<String, String>,
    age: u32,
}

fn main(){
    let mut n = Name { name: HashMap::new(), age: 20 };
    n.name.insert("Yamada".to_string(), "Taro".to_string());
    
    println!("{:?}", n);
}

上記でもできるのはできるけど、一発で書きたい

fromを使えば以下で行ける。

#[derive(Debug)]
struct Name {
    name: HashMap<String, String>,
    age: u32,
}

fn main(){
    let mut n = Name { name: HashMap::from([("yamada".to_string(), "taro".to_string())]), age: 20 };
    println!("{:?}", n);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/sample`
Name { name: {“yamada”: “taro”}, age: 20 }

HashMap::from(“yamada”.to_string(), “taro”.to_string()) だと上手くいかないので注意が必要。セットが一つでも[]で囲ってあげる必要あり。

【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 }