【Rust】axumでget, post, psql

まずテーブル作成
$ sudo -u postgres psql
$ CREATE TABLE “todo” (
id UUID primary key,
description varchar(100) not null,
deadline_at timestamp not null
);

postgres=# \dt
List of relations
Schema | Name | Type | Owner
——–+——–+——-+———-
public | todo | table | postgres

chrono = "0.4.31"
sqlx = { version = "0.7.2", features = ["runtime-tokio-native-tls", "chrono", "uuid"]}
uuid = { version = "1.6.1", fatures = ["v4", "serde"] }
use axum::{Router, extract::State, response::Html, routing::get};
use tera::{Context, Tera};
use serde::{Serialize, Deserialize};
use sqlx::{FromRow, PgPool};
use uuid::Uuid;

//

#[derive(Clone)]
struct ServiceState {
    tera: Tera,
    pool: PgPool,
}

//

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

    let pool = PgPool::connect("postgres://postgres:password@localhost:5432/postgres").await.unwrap();

    let tera = match Tera::new("templates/**/*.html"){
        Ok(t) => t,
        Err(e) => {
            println!("Parsing error(s): {}", e);
            ::std::process::exit(1);
        }
    };

    
    let app = Router::new()
        .route("/", get(index))
        .with_state(ServiceState {tera, pool});
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

http://192.168.33.10:3000/create_todo

### 入力結果の表示
templates/todos.html

<a href="/create_todo">Todoを作成</a>
<table>
    <thread>
        <tr>
            <th scope="col">description</th>
            <th scope="col">deadline at</th>
        </tr>
    </thread>
    <tbody>
        {% for todo in todos %}
        <tr id="{{ todo.id }}">
            <td>{{ todo.description }}</td>
            <td>{{ todo.deadline_at }}</td>
        </tr>
        {% endfor %}
    </tbody>
</table>
use axum::{Router, extract::State, response::{Redirect, Html}, Form, routing::get};
use chrono::NaiveDateTime;
use tera::{Context, Tera};
use serde::{Serialize, Deserialize};
use sqlx::{FromRow, PgPool};
use uuid::Uuid;

pub fn deserialize_date<'de, D: serde::Deserializer<'de>>(
    deserializer: D,
) -> Result<NaiveDateTime, D::Error> {
    let s = String::deserialize(deserializer)?;
    NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M")
        .map_err(serde::de::Error::custom)
}

#[derive(Serialize)]
struct Index {
    name: String
}

#[derive(Clone)]
struct ServiceState {
    tera: Tera,
    pool: PgPool,
}

#[derive(Debug, Serialize, FromRow)]
struct Todo {
    id: Uuid,
    description: String,
    deadline_at: NaiveDateTime,
}

#[derive(Debug, Deserialize)]
struct CreateTodo {
    description: String,
    #[serde(deserialize_with = "deserialize_date")]
    deadline_at: NaiveDateTime,
}

async fn index(State(state): State<ServiceState>) -> Html<String> {
    let index = Index { name: String::from("test") };
    let page = state.tera.render("index.html", &Context::from_serialize(&index).unwrap()).unwrap();
    Html(page.to_owned())
}

async fn get_create_todo(State(state): State<ServiceState>) -> Html<String> {
    let page = state.tera.render("create_todo.html", &Context::new()).unwrap();
    Html(page.to_owned())
}

async fn post_create_todo(
    State(state): State<ServiceState>,
    Form(todo): Form<CreateTodo>,
) -> Redirect {
    let todo = Todo {
        id: Uuid::new_v4(),
        description: todo.description,
        deadline_at: todo.deadline_at,
    };

    sqlx::query("INSERT INTO todo VALUES ($1, $2, $3);")
        .bind(todo.id)
        .bind(todo.description)
        .bind(todo.deadline_at)
        .execute(&state.pool)
        .await
        .expect("todoの取得に失敗しました");

    Redirect::to("/todos")
}

async fn get_todos(
    State(state): State<ServiceState>,
) -> Html<String> {
    let todos = sqlx::query_as::<_, Todo>("SELECT * FROM todo")
        .fetch_all(&state.pool)
        .await
        .expect("todoの取得に失敗しました");
    let mut context = Context::new();
    context.insert("todos", &todos);

    let page = state.tera.render("todos.html", &context).expect("todoの描画に失敗しました");
    Html(page)
}

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

    let pool = PgPool::connect("postgres://postgres:password@localhost:5432/postgres").await.unwrap();

    let tera = match Tera::new("templates/**/*.html"){
        Ok(t) => t,
        Err(e) => {
            println!("Parsing error(s): {}", e);
            ::std::process::exit(1);
        }
    };

    
    let app = Router::new()
        .route("/", get(index))
        .route("/todos", get(get_todos))
        .route("/create_todo", get(get_create_todo).post(post_create_todo))
        .with_state(ServiceState {tera, pool});
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

http://192.168.33.10:3000/todos
Todoを作成
description deadline at
test 2025-01-01T15:54:00
aaa 2025-01-01T15:57:00

なんだこれは…

【Rust】#[derive]アトリビュートとは?

derive: 導出
#[derive]アトリビュートを用いることで、型に対して特定のトレイトの標準的な実装を提供する

比較: Eq, PartialEq, Ord, PartialOrd コンパイルの条件分岐
Clone: &TからT
Copy: to give a type copy semantic
Hash: ハッシュ値計算
Default: 空のインスタンス
Debug: {:?}

implementation

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

fn main(){
    
    let a = Circle{x: 2.0, y: 4.0, radius: 3.0};
    println!("{}", a.area());
}

トレイト

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

trait HasArea {fn area(&self)-> f64;}
impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

fn main(){
    
    let a = Circle{x: 2.0, y: 4.0, radius: 3.0};
    println!("{}", a.area());
}

属性アトリビュートは追加機能
以下のような目的で使用する
– クレート名、バージョン、種類(バイナリか、ライブラリか)の設定
– リントの無効化 (警告の抑止)
– コンパイラ付属の機能(マクロ、glob、インポートなど)の有効化
– 外部ライブラリへのリンク
– ユニットテスト用の関数として明示
– ベンチマーク用の関数として明示
#[test] 単体テスト
#[cfg] 条件を提示し、その条件に応じたコンパイルをする
#[derive] トレイトの実装を自動的に構造体や列挙型に実装できる
 #[derive(Debug)] の場合は、Debugトレイトのfmt関数が実装される
#[allow]
#[deny]

インターフェイスとは?

クラスとは異なり、動作の一覧だけを列挙したもの。
– 中身ができていなくても、暫定的に開発が進められる
– 実装を気にしない、後から修正可能
– 変更しやすくなる
– モデリング力が鍛えられる

import abc

class IMailSender(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def send(self, mail: Mail) -> None:
        raise NotImplementedError()

【Rust】axumを使いたい

### hello world

[dependencies]
axum = "0.7.1"
serde = { version = "1.0.193", features = ["derive"]}
tera = "1.19.1"
tokio = { version = "1.34.0", features = ["full"] }
use axum::{Router, routing::get};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async {
        "hello world"
    }));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

http://192.168.33.10:3000/
hello world

### templateの利用
templates/index.html

hello {{ name }}!
use axum::{Router, response::Html, routing::get};
use tera::{Context, Tera};
use serde::Serialize;

#[derive(Serialize)]
struct Index {
    name: String
}

#[tokio::main]
async fn main() {
    let tera = match Tera::new("templates/**/*.html"){
        Ok(t) => t,
        Err(e) => {
            println!("Parsing error(s): {}", e);
            ::std::process::exit(1);
        }
    };

    let index = Index { name: String::from("test") };
    let page = tera.render("index.html", &Context::from_serialize(&index).unwrap()).unwrap();

    let app = Router::new().route("/", get(|| async move {
        Html(page.to_owned())
    }));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

### with_state

use axum::{Router, extract::State, response::Html, routing::get};
use tera::{Context, Tera};
use serde::Serialize;

#[derive(Serialize)]
struct Index {
    name: String
}

#[derive(Clone)]
struct ServiceState {
    tera: Tera,
}

async fn index(State(state): State<ServiceState>) -> Html<String> {
    let index = Index { name: String::from("test") };
    let page = state.tera.render("index.html", &Context::from_serialize(&index).unwrap()).unwrap();
    Html(page.to_owned())
}

#[tokio::main]
async fn main() {
    let tera = match Tera::new("templates/**/*.html"){
        Ok(t) => t,
        Err(e) => {
            println!("Parsing error(s): {}", e);
            ::std::process::exit(1);
        }
    };

    
    let app = Router::new()
        .route("/", get(index))
        .with_state(ServiceState {tera});
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

【Rust】k256の公開鍵からアドレスを作成する

use k256::{ecdsa::{SigningKey, Signature, signature::Signer, signature::Verifier, VerifyingKey}};
use rand_core::OsRng;
use sha2::{Digest, Sha256};
use ripemd::{Ripemd160};

fn new_keys() {
    let signing_key = SigningKey::random(&mut OsRng);
    let private_key = hex::encode(signing_key.to_bytes());
    println!("private key: {:x?}", private_key);
    let verifying_key = signing_key.verifying_key();
    let public_key = hex::encode(verifying_key.to_sec1_bytes());
    println!("public key: {:x?}", public_key);

    new_address(&verifying_key);    
}   

fn new_address(verifying_key: &VerifyingKey) {

    let vk = verifying_key.to_sec1_bytes();

    let mut hasher = Sha256::new();
    hasher.update(vk);
    let hashed_sha256 = hasher.finalize();

    let mut hasher = Ripemd160::new();
    hasher.update(hashed_sha256);
    let account_id = hasher.finalize();

    let mut payload = account_id.to_vec();
    payload.insert(0, 0x00);

    let mut hasher = Sha256::new();
    hasher.update(&payload);
    let hash = hasher.finalize();

    let mut hasher = Sha256::new();
    hasher.update(hash);
    let checksum = hasher.finalize();

    payload.append(&mut checksum[0..4].to_vec());

    const ALPHABET: &str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
    let address = base_x::encode(ALPHABET, &payload);

    println!("address: {:?}", address);
}


fn main() {
    new_keys();
}

$ cargo run

private key: “745aa1c916085b2e3423dc7a22792945bae57c38bd5bc1bb0426ba2156f8a39c”
public key: “029e1baf2992b44af147c306fb728f8b00e908aa7f09e25eaa0a2fed3f71ad4cf6”
address: “1E5b59jN4nyM9kpzqdXfW7MkLJ2CApAVjT”

これをWebフレームワークで表現したい。使うのはaxumかな。。。

【Rust】sepc256k1の秘密鍵・公開鍵の作成

### 秘密鍵の作成

use k256::{ecdsa::{SigningKey, Signature, signature::Signer, signature::Verifier, VerifyingKey}};
use rand_core::OsRng;

fn new_privatekey() {
    let signing_key = SigningKey::random(&mut OsRng);
    let private_key = signing_key.to_bytes();
    println!("{:x?}", hex::encode(private_key));
}


fn main() {
    new_privatekey();
}

$ cargo run

“69c88f4f82746fbc5ebd0bad102ecf4c83c29eb91eb329961c071cab028a26f1”

rustで点P(2**256-2**32-2**9-2**8-2**7-2**6-2**4-1)を計算しようとすると、bigintのライブラリを使用してもoverflowするので、素直にk256を利用するのが良さそう。

### 公開鍵の追加

fn new_keys() {
    let signing_key = SigningKey::random(&mut OsRng);
    let private_key = hex::encode(signing_key.to_bytes());
    println!("{:x?}", private_key);
    let verifying_key = signing_key.verifying_key();
    let public_key = hex::encode(verifying_key.to_sec1_bytes());
    println!("{:x?}", public_key);
}


fn main() {
    new_keys();
}

$ cargo run

“0d531f6878384de2126386d1c31564c78c4fd5bdc93fc5a15ea8b2a99878b0a8”
“0240a4c5cbca113228016cc39a7bd938dc4b0917eccc97b100450133c48228eb3c”

【Blockchain】ウォレット機能の考察

### ウォレットとして必要な機能
– 秘密鍵、公開鍵の作成、保存
– ブロックチェーン残高の表示
– トランザクションの送信、受信
– ブロックチェーン価格のマーケットデータ表示
※ステーキング、スワッピング、dAppsなど応用機能もある

### 前準備
$ pip install flask
$ pip install web3
$ pip install requests

from aiohttp import request
from flask import Flask, jsonify, render_template, session
from web3 import Web3
import requests
import jsonify

app = Flask(__name__)

infura_url = 'https://mainnet.infura.io/v3/fuga'
app.config['SECRET_KEY'] = 'hoge'

@app.route('/new_account', methods=['GET'])
def new_account():
    account = web3.eth.account.create('hogehoge')
    session['account'] = {
        'privateKey': account.key.hex(),
        'address' : account.address
    }
    return jsonify(session['account'])
with open('erc20_abi.json') as f:
    erc20_abi = json.load(f)

@app.route('/balance/<contract_address>', methods=['GET'])
def get_balance(contract_address):
    address = session.get('account').get('address')
    checksum_address = Web3.to_checksum_address(address)
    print(checksum_address)
    contract = web3.eth.contract(address=contract_address, abi=erc20_abi)
    balance = contract.functions.balanceOf(checksum_address).call()
    return jsonify({'balance': balance})

@app.route('/send_transaction', methods=['POST'])
def send_transaction():
    data = request.get_json()
    nonce = web3.eth.getTransactionCount(session['account']['address'])
    txn_dict = {
        'to': data['to'],
        'value': web3.toWei(data['amount'], 'either'),
        'gas': 2000000,
        'gasPrice': web3.toWei('40', 'gwei'),
        'nonce': nonce,
        'chainId': 3
    }
    signed_txn = web3.eth.account.signTransaction(txn_dict, session['account']['privateKey'])
    txn_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction)
    return jsonify({'transaction_hash': txn_hash.hex()})

@app.route('/market_chart/<contract_address>/<days>', methods=['GET'])
def get_market_chart(contract_address, days):
    api_key = 'coingecho_api_key'
    response = requests.get(f'https://api.coingecko.com/api/v3/coins/ethereum/contract/{contract_address}/market_chart?vs_currency=usd&days={days}&api_key={api_key}')
    market_chart = response.json(f'https://api.coingecko.com/api/v3/coins/ethereum/contract/{contract_address}/market_chart?vs_currency=usd&days={days}&api_key={api_key}')
    market_chart = response.json()
    return jsonify(market_chart)

@app.route('/')
def home():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)

なるほど、walletのエッセンスは理解できました。Pythonでは”web3″のライブラリでかなり抽象化されているのがわかります。
balanceのところはUTXOにするか、Account型にするかで変わってきますね。
マーケットデータは一旦スタック。
テストネット、メインネットの概念はもう少し深掘りする

【Rust】RustでbitcoinのProof of work(PoW)を実装したい

まず、previous hashとnonceを足して、sha256で暗号化します。

use sha2::{Digest, Sha256};
use rand::Rng;

fn main() {
    let previous_hash = "b9b9ee9ffc95fa4956b63b6043a99d0a8f04e0e52e687fc1958d3c6dff885f01";
    let num = rand::thread_rng().gen_range(0..1000000);
    let hash_num = format!("{}{}", previous_hash, num.to_string());
    let header = Sha256::digest(hash_num);
    println!("{:x}", header);
}

$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/crypt`
07b9391fce0c6299097b36a06392a6a6b6245ee59ca816de22aac6dbaf7419af

うむ、やりたいことはできている。ここから、hash値先頭が0000… のheaderになるようなnonceの値を探す計算処理を行う

use sha2::{Digest, Sha256};
use rand::Rng;

fn main() {
    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);

    while target != "0000" {
        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)[..4]).to_string();
        cnt += 1;
    }
    println!("count: {} {:x}", cnt, header);
}

$ cargo run

count: 37455 4b1d6582d1fed66a34041346d0f43cc7b6c2a803588da8c5d515a813c2dcff7a
count: 37456 34b478ac26947b93e63a747be64a06b904fe98953a8db9d4d3f773fdadf5abba
count: 37457 0000bdebe741af3994f4a2160b4480a23ca137aaf0ac51b10fe574f04afc7be4

凄い簡単なコードなんだけど、これ作るのに結構時間かかった…

あとは計算時間を計測してDifficultyの調整機能を作りたい

use sha2::{Digest, Sha256};
use rand::Rng;
use std::{thread, time};

fn main() {
    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);

    while target != "0000" {
        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)[..4]).to_string();
        cnt += 1;
    }
    println!("count: {} {:x}", cnt, header);
    println!("{:?}", now.elapsed());
}

$ cargo run

count: 70736 000074213e839089c9bd8e446dd5835d537cd7037cdf193bf9881df44d2a55b4
1.818378391s

これをmainに取り込む。powはminingとして別ファイルにして、ブロック作成時に読み込むようにする。

mod mining;

fn make_block (){
    mining::proof_of_work();
    println!("blockを作成しました。");
    Pool.lock().unwrap().clear();
}

$ cargo run

count: 16990 0000ea27e22db290e4f2163f968bfaf3ff7d58ccf1cd4ab43b3fbc4326c0eb4a
428.967757ms
blockを作成しました。
8000E340A55A517D0F27F3A63FBE39ED576BA491DFAC89B44654AB147EC66B206B054BAAF53E318EB2721DC892B4736630F400547989AE9F7C069034ECB4DF98

### 課題
– トランザクションプールが出来た後のblock作成のロジックを詰める必要がある。(merkletree, serialize等)
– genesis block, minerへのコイン分配なども考える必要あり。
– トランザクションスピードを上げるために並列処理を導入する場合、どこを並列処理にするのか考える必要あり。

【Rust】rustでrandom intを実装したい

RustでいわゆるPythonのrandintを実装したい

[dependencies]
rand = "0.8"
use rand::Rng;

fn main() {
    let num = rand::thread_rng().gen_range(0..1000000);
    println!("{}", num);
}

$ cargo run
Compiling crypt v0.1.0 (/home/vagrant/dev/rust/crypt)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/crypt`
759081

うん、ここまでは簡単

【Python】pythonでProof of work

from random import randint
from hashlib import sha256

previous_hash = "b9b9ee9ffc95fa4956b63b6043a99d0a8f04e0e52e687fc1958d3c6dff885f01"

cnt = 1

nonce = str(randint(0, 1000000))

header = sha256(f'{previous_hash}{nonce}'.encode()).hexdigest()

print(header)

while header[:4] != "0000":
    text = 'loop:{}, header:{}, header[:4]:{}, nonce:{}\n'
    print(text.format(cnt, header, header[:4], nonce))

    nonce = str(randint(0, 1000000))
    header = sha256(f'{previous_hash}{nonce}'.encode()).hexdigest()
    cnt += 1

text = 'loop:{}, header:{}, header[:4]:{}, nonce:{}\n'
print(text.format(cnt, header, header[:4], nonce))

$ python3 test.py

loop:17576, header:0000ea27e22db290e4f2163f968bfaf3ff7d58ccf1cd4ab43b3fbc4326c0eb4a, header[:4]:0000, nonce:8978

ほう、これをRustで書きたい & 処理時間に応じたdifficulty(0の個数)の調整機能も追加したい。