【Rust】getblockのCLIコマンド機能を作る

CLIでgetblockと引数にblock heightのi32を受け取って、ブロック情報を表示します。

#[derive(Subcommand)]
enum Command {
  Getnewaddress,
  Getinfo,
  Getbalance(BalanceArgs),
  Getaccount,
  Getblock(BlockArgs),
}

fn main() {
   let args = App::parse();
   match args.command {
      Command::Getnewaddress => getnewaddress(),
      Command::Getinfo => getinfo(),
      Command::Getbalance(args) => args.run(),
      Command::Getaccount => getaccount(),
      Command::Getblock(args) => args.run(),
   }
}

#[derive(Args)]
struct BlockArgs {
  height: i32,
}

impl BlockArgs {
  fn run(&self) {
    let _ = cli::get_block(self.height);
  }
}

$ ./target/debug/ruscal getblock 8
Block { height: 8, version: “0.1.0”, time: 2025-02-25T06:48:54.561868021Z, transactions: [SignedTransaction { version: “0.1.0”, time: 2025-02-25T06:47:49.809920514Z, sender: “0410f87429e89498e928d00b6a6186fdc9ccbde2ca55d5746f0e1bf8f1a1fbdb7634de5c1d5b4e49dc29bb78613fefb199d93eb8eb73545791a245c1f1ca6d5ce0”, receiver: “1FsHydJvr3nKj3KcwFhSozBf2WBKMf9jeo”, amount: 50, nft_data: “”, nft_origin: “”, op_code: “”, signature: “8A5216CD62C309A4923E9088CA9284AFC7C0261D89754FCE84735EC34A193A089B7A4C62FC32F91AE0B83549D575CE3D5EBAC1C4B9A61F0EF67D1B851F3F3E53” }, SignedTransaction { version: “0.1.0”, time: 2025-02-25T06:48:45.817409745Z, sender: “0410f87429e89498e928d00b6a6186fdc9ccbde2ca55d5746f0e1bf8f1a1fbdb7634de5c1d5b4e49dc29bb78613fefb199d93eb8eb73545791a245c1f1ca6d5ce0”, receiver: “1FsHydJvr3nKj3KcwFhSozBf2WBKMf9jeo”, amount: 60, nft_data: “”, nft_origin: “”, op_code: “”, signature: “6E73E636DDA5A5F360280442BEA308CD3C38C7A4C769BC8D4965B5106F9F6647D15B0382DE90ECFB84AE462BB81F26099DB1F8A9F0276541C44C60376FF31D16” }], hash: “0000ddb477024b0fb97c02e34bfad38eaee9891524759ba8801e06283b12cb9c”, nonce: “270778”, merkle: “713f5eb6109b03eb22cf8236171040f6bbf6171fbb2513b782b8b9fa9f41807f”, miner: “MTYwLjE2LjExNy44MA==”, validator: “MTYwLjE2LjExNy44MA==”, op: “” }

同じような容量で、gettransactionも同様に作ります。
$ ./target/debug/ruscal gettransaction 8A5216CD62C309A4923E9088CA9284AFC7C0261D89754FCE84735EC34A193A089B7A4C62FC32F91AE0B83549D575CE3D5EBAC1C4B9A61F0EF67D1B851F3F3E53

transactionの送信の場合

#[derive(Args)]
struct MoveArgs {
  pub_key: String,
  address: String,
  amount: i32
}

impl MoveArgs {
  fn run(&self) {
    // transaction送信関数を呼び出す
  }
}

$ ./target/debug/ruscal move de2ca55d5746f0e1bf8f1a1fbdb7634de5c1d5b4e49dc29bb78613fefb199d93eb8eb73545791a245c1f1ca6d5ce0 12sSVCmfi7kgsdG4ZmaargPppDRvkE5zvD 1000

なるほど、大分わかってきました。

【Rust】getnewaddressのAPI機能を作る

### コマンドライン引数の書き方

fn main() {

   let command_name = std::env::args().nth(0).unwrap_or("CLI".to_string());
   let name = std::env::args().nth(1).unwrap_or("World".to_string());

   println!("Hello {} via {}!", name, command_name);
}

$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/ruscal`
Hello World via target/debug/ruscal!

### 上記を応用する

fn main() {
   let args = App::parse();
   match args.command {
    Command::Getnewaddress => getnewaddress(),
   }
}

fn getnewaddress() {
  address::get_address();
}
use p256::{
    ecdsa::{SigningKey},
    pkcs8::EncodePrivateKey, PublicKey,SecretKey};
use rand_core::{OsRng};
use std::{fs::File, io::Write, path::Path};
use ripemd::{Ripemd160};
use sha2::{Digest, Sha256};

pub fn get_address() {
    init_secret();
    let contents = std::fs::read_to_string("./config/tmp.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 _public_key_serialized = hex::encode(&public_key.to_sec1_bytes());
    let address = create_address(&public_key);  
    println!("{}", address);
}

pub fn init_secret() {
    let path = Path::new("./config/tmp.pem");
    if path.is_file() {
        // println!("The private key, public key, and address have already been created.");
    } else {
        let secret_key = SigningKey::random(&mut OsRng);
        let secret_key_serialized = secret_key
            .to_pkcs8_pem(Default::default())
            .unwrap()
            .to_string();
        let mut file = File::create("./config/tmp.pem").expect("file not found.");
        writeln!(file, "{}", secret_key_serialized).expect("can not write.");
        println!("created a private key.");
    }
}

pub fn create_address(public_key: &PublicKey) -> String {

    let vk = public_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);

    return address;
}

$ cargo run getnewaddress
Compiling ruscal v0.1.0 (/home/vagrant/dev/rust/api)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.92s
Running `target/debug/ruscal getnewaddress`
1BnpTXdSbSgMZwWYq7cdU3LPSw1g4YhyHZ

【IPFS】Merkle DAGをPythonで書く

import hashlib

class Node:
    def __init__(self, data=None, children=None):
        self.data = data
        self.children = children or []
        self.hash = self.compute_hash()

    def compute_hash(self):
        m = hashlib.sha256()
        if self.data:
            m.update(self.data.encode('utf-8'))
        for child in self.children:
            m.update(child.hash.encode('utf-8'))
        return m.hexdigest()

    def __repr__(self):
        return f"Node(hash={self.hash[:10]}, data={self.data})"

# ノードを作成
leaf1 = Node(data="block A")
leaf2 = Node(data="block B")
leaf3 = Node(data="block C")

# 同じ子を共有するノード
intermediate1 = Node(children=[leaf1, leaf2])
intermediate2 = Node(children=[leaf2, leaf3])

# DAGのルート
root = Node(children=[intermediate1, intermediate2])

# 表示
print("Merkle DAG:")
print("Root:", root)

$ python3 merkledag.py
Merkle DAG:
Root: Node(hash=b7026ffa9b, data=None)

同じハッシュを持つノードを共有
leaf2 は2つの親ノードに共有されており、DAGならでは構造となっている。IPFSやGitで使われる構造

【Ethereum】erc20とethをswap

pragma solidity ^0.8.0;

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

contract SimpleSwap {
    address public owner;
    IERC20 public token;
    uint256 public rate;

    constructor(address _tokenAddress, uint256 _rate) {
        owner = msg.sender;
        token = IERC20(_tokenAddress);
        rate = _rate;
    }

    receive() external payable {
        uint256 tokenAmount = msg.value * rate;
        require(token.transfer(msg.sender, tokenAmount), "Token transfer failed");
    }

    function swapTokenForETH(uint256 tokenAmount) external {
        uint256 ethAmount = tokenAmount / rate;
        require(address(this).balance >= ethAmount, "Not enough ETH in contract");

        require(token.transferFrom(msg.sender, address(this), tokenAmount), "Token transfer failed");

        (bool success, ) = msg.sender.call{value: ethAmount}("");
        require(success, "ETH transfer failed");
    }

    function withdrawETH() external {
        require(msg.sender == owner, "Not owner");
        payable(owner).transfer(address(this).balance);
    }

    function withdrawTokens() external {
        require(msg.sender == owner, "Not owner");
        uint256 balance = tokenBalance();
        require(token.transfer(owner, balance), "Token withdrawal failed");
    }

    function tokenBalance() public view returns (uint256) {
        return token.balanceOf(address(this));
    }
}

【Ethereum】送信者が特定の条件の元、ethを送金

sendではなく、recipient.callで送信してますね。

pragma solidity ^0.8.0;

constract ConditionTransfer {
    address public owner;
    address payable public recipient;
    uint256 public unlockTime;

    constructor(address payable _recipient, uint256 _unlockTime) {
        owner = msg.sender;
        recipient = _recipient;
        unlockTime = _unlockTime;
    }

    receive() external payable {}

    function release() external {
        require(msg.sender == owner, "Only owner can release funds");
        require(block.timestamp >= unlockTime, "Funds are locked");

        uint256 amount = address(this).balance;
        require(amount > 0, "No funds to send");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Transfer failed");
    }
}

【Python】kecchak256

kecchak256は、文字列をランダムな256ビットの16進数にマッピングする機能
ethereumで使用されている

$ pip3 install pysha3

import sha3

data = b'Hello, keccak256!'

keccak = sha3.keccak_256()
keccak.update(data)
hash_hex = keccak.hexdigest()

print("keccak256 hash:", hash_hex)

$ python3 keccak256.py
keccak256 hash: 0fa5c2f72a50589fff02f2f5105ba40d97c01e114df6fd00cc4b1fc0afbec43a

【Ethereum】ERC721

ERC721は非代替トークンのNFT規格の一つ

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC721, Ownable {
    constructor(address initialOwner)
        ERC721("HFToken", "HogeFuga") // MyToken..トークン名 MTK..トークン名の略称
        Ownable(initialOwner)
    {}

    function safeMint(address to, uint256 tokenId) public onlyOwner {
        _safeMint(to, tokenId);
    }
}

【Ethereum】Solidityの基礎2

# Solidityのオブジェクト
## msg オブジェクト
– msg.sender: address
– msg.value: eth value
– msg.gas
– msg.data

## txオブジェクト
– tx.gasprice
– tx.origin: transaction address

## blockオブジェクト
– block.coinbase
– block.difficulty
– block.gaslimit
– block.number
– block.timestamp

## addressオブジェクト
– address.balance
– address.transfer
– address.send

## 関数
addmod, mulmod, keccak256, sha3, sha256, erecover, selfdestruct, this

## その他
– external: コントラクトの外部
– internal: コントラクトの内部
– view: データの閲覧のみ
– pure: 読み取りも行わない
– event: ブロックチェーン上で何かが生じた時、web上やアプリに伝えることができる仕組み

## ECR20

pragma solidity ^0.8.17;

interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes calldata _extraData) external; }

contract TokenERC20 {
    string public name;
    string public symbol;
    uint8 public decimals = 18;
    uint256 public totalSupply;

    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed _owner, address indexed _sender, uint256 _value);
    event Burn(address indexed from, uint256 value);

    constructor(
        uint256 initialSupply,
        string memory tokenName,
        string memory tokenSymbol
    ) public {
        totalSupply = initialSupply * 10 ** uint256(decimals);
        balanceOf[msg.sender] = totalSupply;
        name = tokenName;
        symbol = tokenSymbol;
    }

    function _transfer(address _from, address _to, uint _value) internal {
        require(_to != address(0));
        require(balanceOf[_from] = _value);
        require(balanceOf[_to] + _value = balanceOf[_to]);
        uint previousBalances = balanceOf[_from] + balanceOf[_to];
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(_from, _to, _value);
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        _transfer(msg.sender, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public
        returns (bool success) {
            allowance[msg.sender][_spender] = _value;
            emit Approval(msg.sender, _spender, _value);
            return true;
        }

    function approveAndCall(address _spender, uint256 _value, bytes memory _extraData)
        public 
        returns (bool success) {
            tokenRecipient spender = tokenRecipient(_spender);
            if (approve(_spender, _value)) {
                spender.receiveApproval(msg.sender, _value, address(this), _extraData);
                return true;
            }
        }

    function burn(uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] = _value);
        balanceOf[msg.sender] -= _value;
        totalSupply -= _value;
        emit Burn(msg.sender, _value);
        return true;
    }

    function burnFrom(address _from, uint256 _value) public returns (bool success) {
        require(balanceOf[_from] = _value);
        require(_value = allowance[_from][msg.sender]);
        balanceOf[_from] -= _value;
        allowance[_from][msg.sender] -= _value;
        totalSupply -= _value;
        emit Burn(_from, _value);
        return true;
    }
}

from solidity:
TypeError: No matching declaration found after argument-dependent lookup.
–> ecr20.sol:31:8:
|

ん? 何故だ…

【Ethereum】トレーサビリティのsmart contractを考える

単純にアドレスに保存するだけであれば、mapping(address => Data) accounts;とすれば良さそう。
ただし、この場合は、商品ごとにアドレスを持っていないといけない。
緯度経度やstatusの管理は、フロントエンド側(React)で実装か…
memoryとstorageの仕様詳細が欲しいところ。。。

pragma solidity ^0.8.17;

contract Traceability {

    struct Data {
        uint256 id;
        string name;
        string latitude;
        string longitude;
        string status;
    }

    address[] public users;

    mapping(address => Data) accounts;

    function registerAccount(uint256 _id, string memory _name, string memory latitude, string memory longitude, string memory status)
        public 
        returns (bool)
    {
        accounts[msg.sender].id = _id;
        accounts[msg.sender].name = _name;
        accounts[msg.sender].latitude = latitude;
        accounts[msg.sender].longitude = longitude;
        accounts[msg.sender].status = status;
        
        users.push(msg.sender);
        return true;
    }

    function ViewAccount(address user) public view returns (uint256, string memory, string memory, string memory, string memory) {
        uint256 _id = accounts[user].id;
        string memory _name = accounts[user].name;
        string memory _latitude = accounts[user].latitude;
        string memory _longitude = accounts[user].longitude;
        string memory _status = accounts[user].status;

        return (_id, _name, _latitude, _longitude, _status);
    }

}

ethの受け取り

pragma solidity ^0.8.17;

contract RecvEther {
    address public sender;

    uint public recvEther;

    function () payable {
        sender = msg.sender;
        recvEther += msg.value;
    }
}

【Ethereum】solidity 基礎

memoryは処理中のみ保存し、storageは処理後、ブロックチェーンに保存される。

pragma solidity ^0.8.17;

contract Register {
    struct Data {
        string name;
        uint256 age;
        string hobby;
    }

    // 全ユーザのアドレスを保存
    address[] public users;

    mapping(address => Data) accounts;

    function registerAccount(string memory _name, uint256 _age, string memory _hobby) 
        public 
        returns (bool)
    {
        if (!isUserExist(msg.sender)) {
          users.push(msg.sender);
        }
        // msg.sender はトランザクション送信者
        accounts[msg.sender].name = _name;
        accounts[msg.sender].age = _age;
        accounts[msg.sender].hobby = _hobby;
        return true;
    }

    function isUserExist(address user) public view returns (bool) {
        for (uint256 i = 0; i < users.length; i++) {
            if (users[i] == user) {
                return true;
            }
        }
        return false;
    }

    function ViewAccount(address user) public view returns (string memory, uint256, string memory) {
        string memory _name = accounts[user].name;
        uint256 _age = accounts[user].age;
        string memory _hobby = accounts[user].hobby;

        return (_name, _age, _hobby);
    }
}

remixでデプロイのテストができる

metamaskアカウントでsepoliaにdeployする
https://sepolia.etherscan.io/

フロントエンド

import React from "react";
import Resister from "./Register.json";
import getWeb3 from "./getWeb3";
// import "./App.css";
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      web3: null,
      accounts: null,
      contract: null,
      name: null,
      age: null,
      hobby: null,
      address: "",
      outputName: null,
      outputAge: null,
      outputHobby: null,
    };
  }

  componentDidMount = async () => {
    try {
      const web3 = await getWeb3();

      const accounts = await web3.eth.getAccounts();
      const networkId = await web3.eth.net.getId();
      const deployedNetwork = Resister.networks[networkId];
      const instance = new web3.eth.Contract(
        Resister.abi,
        deployedNetwork && deployedNetwork.address
      );

      this.setState({ web3, accounts, contract: instance });
    } catch (error) {
      alert(
        `Failed to load web3, accounts, or contract. Check console for details.`
      );
      console.error(error);
    }

    const { accounts } = this.state;
    console.log(accounts);
  };

  // アカウント情報の登録
  writeRecord = async () => {
    const { accounts, contract, name, age, hobby } = this.state;
    const result = await contract.methods.registerAccount(name, age, hobby).send({
      from: accounts[0],
    });
    console.log(result);

    if (result.status === true) {
      alert('会員登録が完了しました。');
    }
  };

  // アカウント情報の読み込み
  viewRecord = async () => {
    const { contract, accounts } = this.state;
    console.log(contract);

    const result = await contract.methods.viewAccount(accounts[0]).call();
    console.log(result);

    const outputName = result[0];
    const outputAge = result[1];
    const outputHobby = result[2];
    this.setState({ outputName, outputAge, outputHobby });
  };

  handleChange = (name) => (event) => {
    this.setState({ [name]: event.target.value });
  };

  render() {
    return (
      <div className="App">
        <br />
        <form>
          <div>
            <label>氏名:</label>
            <input
              onChange={this.handleChange("name")} />
          </div>

          <div>
            <label>年齢:</label>
            <input
              onChange={this.handleChange("age")} />
          </div>

          <div>
            <label>趣味:</label>
            <input
              onChange={this.handleChange("hobby")} />
          </div>

          <button type='button' onClick={this.writeRecord}>
            会員登録
          </button>
        </form>

        <br />
        <br />

        <form>
          <label>検索したいアドレスを入力してください。</label>
          <input onChange={this.handleChange("address")} />

          <button type='button' onClick={this.viewRecord}>
            検索
            </button>
        </form>

        <br />
        <br />

        {this.state.outputName ? <p>氏名: {this.state.outputName}</p> : <p></p>}
        {this.state.outputAge ? <p>年齢: {this.state.outputAge}</p> : <p></p>}
        {this.state.outputHobby ? <p>趣味: {this.state.outputHobby}</p> : <p></p>}

      </div>

    );
  }
}

export default App;

ちょっと記事が古いので期待通りに動かないが、