-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 327 KB
/
content.json
1
{"meta":{"title":"TID's Blog","subtitle":null,"description":"每一篇文章都是一种进步,每一句感慨都是一次思考","author":"TigerInYourDream","url":"https://imzy.vip","root":"/"},"pages":[{"title":"categories","date":"2020-05-12T10:24:44.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"categories/index-1.html","permalink":"https://imzy.vip/categories/index-1.html","excerpt":"","text":""},{"title":"分类","date":"2019-03-25T00:51:33.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"categories/index.html","permalink":"https://imzy.vip/categories/index.html","excerpt":"","text":""},{"title":"友链","date":"2020-11-26T11:21:35.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"friends/index.html","permalink":"https://imzy.vip/friends/index.html","excerpt":"","text":"推荐博客CPP/UE : Z’s Blog"},{"title":"一言","date":"2019-03-25T09:32:23.000Z","updated":"2024-08-12T16:01:47.554Z","comments":true,"path":"motto/index.html","permalink":"https://imzy.vip/motto/index.html","excerpt":"","text":"使用gist管理一言"},{"title":"标签","date":"2019-03-25T00:53:36.000Z","updated":"2024-08-12T16:01:47.554Z","comments":true,"path":"tags/index.html","permalink":"https://imzy.vip/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"为 C++程序写 rustbinding","slug":"为-C-程序写-rustbinding","date":"2024-08-12T23:24:49.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/12714/","link":"","permalink":"https://imzy.vip/posts/12714/","excerpt":"为 C++程序写 rustbindingAutoCxx 与 CWrapper+Bindgen为 c++程序写 ruts-binding 在代码的世界中,还是 c和 cpp站绝大多数,现在提一个比较常见的需求:提供一个 c++的程序,最终需要再 rust中调用 c++程序提供的接口。 一般来说有两个方法 直接使用 cxx autocxx为 rust代码生成一份 unsafe的代码,然后直接调用 第二种方法比较路径稍长,先针对 c++代码的 header 写一份 c风格的头文件cwrapper,然后针对 c的头文件写一份 c头文件的实现。接下来编译自己的cwrapper,生成一份新的动态库。接下来使用 bindgen 根据 cwrapper生成一份 unsafe rust。最后在 rust代码中调用。 总体来说 cxx 或者 autocxx 可能性能会更好一些,但是 autocxx并不能搞定一切。第二种方法胜在稳定,毕竟 c的 abi比较稳定。本文将采用后一种方法。 速成材料技术基础是:会 rust,不会 c++或者 c。所以需要速成,了解 c和 c++。如果彻底不会 c++,写 bingen 无从谈起。 下面是一些材料","text":"为 C++程序写 rustbindingAutoCxx 与 CWrapper+Bindgen为 c++程序写 ruts-binding 在代码的世界中,还是 c和 cpp站绝大多数,现在提一个比较常见的需求:提供一个 c++的程序,最终需要再 rust中调用 c++程序提供的接口。 一般来说有两个方法 直接使用 cxx autocxx为 rust代码生成一份 unsafe的代码,然后直接调用 第二种方法比较路径稍长,先针对 c++代码的 header 写一份 c风格的头文件cwrapper,然后针对 c的头文件写一份 c头文件的实现。接下来编译自己的cwrapper,生成一份新的动态库。接下来使用 bindgen 根据 cwrapper生成一份 unsafe rust。最后在 rust代码中调用。 总体来说 cxx 或者 autocxx 可能性能会更好一些,但是 autocxx并不能搞定一切。第二种方法胜在稳定,毕竟 c的 abi比较稳定。本文将采用后一种方法。 速成材料技术基础是:会 rust,不会 c++或者 c。所以需要速成,了解 c和 c++。如果彻底不会 c++,写 bingen 无从谈起。 下面是一些材料 https://www.youtube.com/watch?v=KJgsSFOSQv0&t=76s https://www.youtube.com/watch?v=ZzaPdXTrSb8 https://www.runoob.com/cplusplus/cpp-variable-scope.html 第一个为 feeCodeCamp的 c课程,三小时速成。 因为 c++比较难,可以学习第二个教程一小时速成,接下来看菜鸟教程的文档。注意不要纠结细节,否则无法速成。学成 C++已经数年以后 🥳,毕竟最终目的并非写 c++。 或者也可以看 freecodecamp的 c++教程,大概四小时看完。 CPP部分库文件准备https://github.com/TigerInYourDream/cppExample c++部分代码已经上传 github。常见的c++项目大概使用 cmake编译,因为速成材料中没有讲 cmake,所以直接用 g++或者 clang编译。 123456789101112131415161718192021222324252627#include <ctime>#include <iostream>using namespace std;namespace MyNamespace { class MyClass { public: MyClass(); ~MyClass(); void myMethod(); };}MyNamespace::MyClass::MyClass() { cout << "Constructor called" << endl;}MyNamespace::MyClass::~MyClass() { cout << "Destructor called" << endl;}void MyNamespace::MyClass::myMethod() { cout << "myMethod called" << endl;}// int main() {// MyNamespace::MyClass obj;// obj.myMethod();// return 0;// } 12345678910111213//下面的是头文件#ifndef MYCLASS_HPP#define MYCLASS_HPPnamespace MyNamespace { class MyClass { public: MyClass(); ~MyClass(); void myMethod(); };}#endif // MYCLASS_HPP c++源文件和头文件在此。一个非常简单的代码,为了在后续使用 c风格的 wrapper。特意使用了 namespace class这些 c没有的特性。大致解释下代码 分别有构造函数,析构函数(类似与 rust的 Drop)和一个成员函数(或者这个称为方法)。 clang++ -c -fPIC MyClass.cpp -o MyClass.o clang++ -dynamiclib -o libMyClass.dylib MyClass.o -dynamiclib 选项表示生成动态库。 -o libMyClass.dylib 指定输出文件的名称为 libMyClass.dylib。 c++的二进制产物生成分两步 编译 链接 编译生成 .o的编译产物,然后链接生成动态库。因为我的编程环境为 mac,所以我使用 clang++ 且选择生成 dylib。如果是 linux考虑使用 g++和生成 so(这一类更常见) C包装首先根据暴露的库文件包装一个 c风格的头 123456789101112131415#ifndef MYCLASSWRAPPER_H#define MYCLASSWRAPPER_H#ifdef __cplusplusextern "C" {#endiftypedef struct MyClassOpaque MyClassOpaque;/* typedef (struct MyClassOpaque) MyClassOpaque; */typedef MyClassOpaque* MyClassHandle;MyClassHandle MyClass_create();void MyClass_destroy(MyClassHandle handle);void MyClass_myMethod(MyClassHandle handle);#ifdef __cplusplus}#endif#endif // MYCLASSWRAPPER_Htypedef struct MyClassOpaque MyClassOpaque; c的头文件如上所示,关键是使用不透明指针 不透明指针(Opaque Pointer)是一种特殊类型的指针,它隐藏了所指向的具体数据类型的详细信息。不透明指针只提供了指针的操作,而不暴露指针所指向的数据的类型和结构。 typedef struct MyClassOpaque MyClassOpaque; 是一种特别的写法 实质相当于 对 struct MyClassQpaque 的别名,以后用MyClassOpaque 不用带 Struct关键字。 typedef MyClassOpaque* MyClassHandle; 直接定义不透明指针 有了这个C风格的头还不够,还需要一份实现代码 12345678910111213141516171819202122#include "MyClassWrapper.h"#include "myclass.hpp"using namespace MyNamespace;extern "C" { struct MyClassOpaque { MyClass* instance; }; MyClassHandle MyClass_create() { MyClassOpaque* opaque = new MyClassOpaque; opaque->instance = new MyClass(); return opaque; } void MyClass_destroy(MyClassHandle handle) { MyClassOpaque* opaque = static_cast<MyClassOpaque*>(handle); delete opaque->instance; delete opaque; } void MyClass_myMethod(MyClassHandle handle) { MyClassOpaque* opaque = static_cast<MyClassOpaque*>(handle); opaque->instance->myMethod(); }} 实际实现的代码如上。使用 exrern “C” 包装 相当于 rust的 extern “C” 和 nomangle。注意对应实现析构函数的 destroy函数,注意 delete内存。 接下来 123456clang++ -c -fPIC MyClassWrapper.cpp -o MyClassWrapper.oclang++ -dynamiclib -o libMyClassWrapper.dylib MyClassWrapper.o -L . -lMyClass-dynamiclib 选项表示生成动态库。-o libMyCombined.dylib 指定输出文件的名称为 libMyCombined.dylib。-L. 选项告诉链接器在当前目录中查找库文件。-lMyClass 选项告诉链接器链接到 libMyClass.dylib 库文件。 就是编译 接下来链接第一次生成c++的动态库 MyClass。 注意生成的动态库有 lib前缀。他们的依赖关系如下MyClassWrapper 链接 MyClass. 这个库的链接非常重要。 现在就有了一份 c风格的头文件和两个动态库 MyClass MyClassWrapper 本人对 c++并不熟悉如果有其他注意的点好改进,欢迎提出改进 Rust部分https://github.com/TigerInYourDream/bindexample rust部分直接选择使用 bindgen.生成 rust代码 123456789101112├── Cargo.lock├── Cargo.toml├── build.rs├── include│ └── MyClassWrapper.h├── lib│ ├── libMyClass.dylib│ └── libMyClassWrapper.dylib├── src│ ├── bindings.rs│ └── main.rs├── target rust项目的结构如上所示。include中为 c风格的头文件。主要注意的点存在于 build.rs中 12345678910111213141516171819extern crate bindgen;use std::path::PathBuf;use bindgen::CargoCallbacks;fn main() { println!("cargo:rustc-link-search=native=./lib"); println!("cargo:rustc-link-lib=dylib=MyClassWrapper"); println!("cargo:rustc-link-lib=dylib=MyClass"); // 生成 Rust 绑定 let bindings = bindgen::Builder::default() .header("wrapper.h") .parse_callbacks(Box::new(CargoCallbacks::new())) .generate() .expect("Unable to generate bindings"); // 将生成的绑定写入 src/bindings.rs 文件 let out_path = PathBuf::from("./src/"); bindings .write_to_file(out_path.join("bindings.rs")) .expect("Couldn't write bindings!");} 技巧 生成代码可以直接生成到 src目录下,否则会直接生成到 build目录下,也就是环境变量 OUT_DIR输出的环境的。可以生成到src 手动引用。这样生成的代码可以像正常代码一样可以被正常引用,也可以直接使用 rust analysis 分析。 如果使用 cspell记得单独排除这个文件。 可以在根目录下外加一个 wrapper.h文件,在 wrapper中指定外部的头文件。或者也可以参考 bingen的最佳实践。目前个人最佳实践是这样。 三个打印分别是 link-search 目录,下面两个是具体搜索的库,不要带前缀和后缀。 运行的注意点 注意 build.rs只会管 build时刻的链接目录,运行的时候并不会管。如果编译的时候提示找不到动态库,可以修改 search目录,或者仔细观察目录,把库的目录直接移动到项目根目录下(因为根目录也是默认的库搜索路径),还有很多其他路径,可以可以删除观察。 cargo r氛围两阶段,一个是 build阶段,build阶段 build.rs中的设置是有用的。第二个阶段为运行,相当于执行 ./xxxx。 所以直接 cargo r -r 会找不到库路径 1234export DYLD_LIBRARY_PATH=/path/to/dylib:$DYLD_LIBRARY_PATH直接使用上面的环境变量设置动态库的文件目录 注意上面的适用于 MAC的 dyliblinux 则是设定LD_LIBRARY_PATH 可以使用 just来设置环境变量 和 一组编译运行计划来简化命令行。因为 just和和编写 bindgen 无关,随意不在本文提起。 然后就可以执行了","categories":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/categories/rust/"}],"tags":[{"name":"c++","slug":"c","permalink":"https://imzy.vip/tags/c/"},{"name":"bingen","slug":"bingen","permalink":"https://imzy.vip/tags/bingen/"}]},{"title":"sonala合约","slug":"sonala合约","date":"2024-06-12T20:00:49.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/43396/","link":"","permalink":"https://imzy.vip/posts/43396/","excerpt":"solana合约学习本文是对 solan相关问题介绍,主要用于记录solana合约的学习过程。因为现阶段还没有十分深入的学习 solana的细节,所以目标限定在 solana的简单合约编写,环境搭建,测试。 基础因为是学习指南,所以会先强调学习的基础。本文的基础建立在会 rust基础上,明白 rust的语法,可以达到 rust编写简单代码的基础之上。因为 solana 合约是直接使用 rust编写的。如果不具备简单的 rust基础,请先学习 rust book,至少了解 rust的基本语法。 solana环境因为 solana有自己的介绍文档,不再重复介绍solana概念。下面主要讲剩余部分。 开发环境 主要就是 rust环境,可以去 rust官网上直接安装 rust环境。因为 rust已经比较成熟,没有特别的问题,直接安装最新环境。 编辑器,因为是直接使用 rust开发solana 合约的,所以主流的 VSCODE Rustrover vim zed emacs都可以。选择自己喜欢的就好。 solana cli工具。https://solana.com/developers/guides/getstarted/local-rust-hello-world cli工具是生成地址,链接 solana节点,部署合约的基础。后面会继续提到一些注意事项,请务必参考安装的的注意事项。 anchor anchor是 solana 的“框架”,可以去 anchor官网安装 solana框架。后面也有注意事项。 以上的套件理论上是全部需要的。如果你只是使用solana native开发,可以不使用 anchor。也就可以不用安装 anchor 套件。 环境安装中的特殊问题 根据目前 2024年06月08日20:17:14 时间 solana 是 v1.18.15。 solana 本身并不是直接安装最新版本就好了。最新版本可能出现编译不过,获取有一些更加具体的字节超量的编译问题。这时候就需要回退到历史版本。可以看下面的链接。当然如果你非常幸运,安装之后完全没有问题就不必折腾。事实是,我安装了最新版之后一直无法通过编译,然后不得不安装下面的方案回退。 https://www.notion.so/alvinvip/cd22ecfe315d4acbb83c68ca8a3d7b30?pvs=4#14fa26df7432402ebf18529fecf20e40 anchor也存在以上问题,最新版无法运行。这样采用上面的办法即可,直接退版本。 https://book.anchor-lang.com/getting_started/installation.html 注意查看版本关系 anchor 0.30需要使用最新的 solana v1.18.15(务必注意时间,后续升级必须兼顾 solana版本和 anchor版本) 测试问题","text":"solana合约学习本文是对 solan相关问题介绍,主要用于记录solana合约的学习过程。因为现阶段还没有十分深入的学习 solana的细节,所以目标限定在 solana的简单合约编写,环境搭建,测试。 基础因为是学习指南,所以会先强调学习的基础。本文的基础建立在会 rust基础上,明白 rust的语法,可以达到 rust编写简单代码的基础之上。因为 solana 合约是直接使用 rust编写的。如果不具备简单的 rust基础,请先学习 rust book,至少了解 rust的基本语法。 solana环境因为 solana有自己的介绍文档,不再重复介绍solana概念。下面主要讲剩余部分。 开发环境 主要就是 rust环境,可以去 rust官网上直接安装 rust环境。因为 rust已经比较成熟,没有特别的问题,直接安装最新环境。 编辑器,因为是直接使用 rust开发solana 合约的,所以主流的 VSCODE Rustrover vim zed emacs都可以。选择自己喜欢的就好。 solana cli工具。https://solana.com/developers/guides/getstarted/local-rust-hello-world cli工具是生成地址,链接 solana节点,部署合约的基础。后面会继续提到一些注意事项,请务必参考安装的的注意事项。 anchor anchor是 solana 的“框架”,可以去 anchor官网安装 solana框架。后面也有注意事项。 以上的套件理论上是全部需要的。如果你只是使用solana native开发,可以不使用 anchor。也就可以不用安装 anchor 套件。 环境安装中的特殊问题 根据目前 2024年06月08日20:17:14 时间 solana 是 v1.18.15。 solana 本身并不是直接安装最新版本就好了。最新版本可能出现编译不过,获取有一些更加具体的字节超量的编译问题。这时候就需要回退到历史版本。可以看下面的链接。当然如果你非常幸运,安装之后完全没有问题就不必折腾。事实是,我安装了最新版之后一直无法通过编译,然后不得不安装下面的方案回退。 https://www.notion.so/alvinvip/cd22ecfe315d4acbb83c68ca8a3d7b30?pvs=4#14fa26df7432402ebf18529fecf20e40 anchor也存在以上问题,最新版无法运行。这样采用上面的办法即可,直接退版本。 https://book.anchor-lang.com/getting_started/installation.html 注意查看版本关系 anchor 0.30需要使用最新的 solana v1.18.15(务必注意时间,后续升级必须兼顾 solana版本和 anchor版本) 测试问题 solana是使用 rust写的合约,完全可以使用 rust代码来测试。但是,务必记住,如果你使用 anchor的话,直接使用 TS测试。TS包提供了完整的测试环境和方法,非常方便。在 solana的世界中,使用 TS测试是常态。使用 rust测试反而是异类。应该还是 TS程序员更加普遍的原因,优先支持更流行的语言。 所以在会 rust的基础上可以,快速学习一下 js语法,然后学一点 TS即可。 https://www.youtube.com/watch?v=vDNw0FWL8zw&ab_channel=走歪的工程師James 在 youtube上随意找到一个 js教学视频,大概两小时可以了解 js用法。然后再花 20 分钟学习一点 TS和 js的不同之处即可。所以没错,这个学习过程中包括两个半小时的 js/ts基础的学习。学习到可以写代码即可。 如果一定要用 rust测试也没有问题 1anchor init —test-template rust <xxxxx> 使用以上命令可以生成 带rust测试模板的 anchor 代码。 https://github.com/coral-xyz/anchor/pull/2805 上面的 PR也是关于 rust测试的,可以看出来anchor对于 rust测试支持也是最近才有一定的进展。所以建议还是现学TS测试比较快。 节点大部分时候节点使用不应该成为一个问题,但是 solana的 devnet似乎很不稳定,所以最好使用本地网络部署的和测试 123solana-test-validator solana-test-validator -r 上面的两个命令是启动本地节点和 重启本地节点。如果你在启动节点的时候 7 遇到问题可以考虑这个。主要的症状是再次重启,节点状态不对。 和同等类型合约的差别众所周知,使用 rust编写的区块链还有 sui和 aptos。在 solana合约之前,我还学习了 move合约 https://github.com/TigerInYourDream/letsmove 上面是我参与 sui 合约的代码。sui的合约使用 move编写。和 anchor不一样,move的环境安装没有上面的特殊问题。直接安装最新就可以。move是一个简易版本的 rust,学习难度显著降低。 另外 sui的主要交互可以使用命令行实现,主要参数就是部署时刻生成的各种 hash id。可能对于后端程序员这这种方式更加直接。 但是 sui move目前支持的功能相对少一点。所以两种合约各有优缺点。 solana的抽象层次和所有的区块链编程模型一样 solana划分为下面的结构 solana使用合约直接使用 rust编写。写好的合约可以直接部署到 solana的节点上。这个过程称之为发布合约。solana因为使用 rust,和其他区块链稍有不同,把编程部分成为 program。他也确实是个完备的 program。为了和其他区块链中的概念统一,也可以把它称为智能合约。 部署节点上的合约,就相当于给 solana节点增加了新的“接口”。可以调用 solana client sdk直接调用合约实现交互。最直接的使用 solana js sdk就可以实现前端网页与合约交互。使用 sdk与节点交互的这个应用就是 dapp。 和金融里面合约不一样,区块链世界的“合约”就是一段可以执行的代码。所以有的区块链也把自己称之为“互联网计算机”。能执行代码,也能被调用,的确是是一个“计算机”。 此外,让链上数据发生变化的操作叫做交易。无论是通过 dapps 或者 rust js 客户端或者其他方式和链上信息进行交互的过程都叫做交易。 anchor和 solana的关系在 solana链上实现编程主要有两种方法 native program anchor https://solana.com/developers/guides/getstarted/local-rust-hello-world 使用以上代码, 引入 solana-program crates就可以进行 native solana program。如果你仔细查看代码, 你会发现指令 Instration是个 u8数组,也就是说进行网路传输的数据,你需要在合约段解码u8,相应的你需要在客户端进行编码。没错,你还需要了解编解码和 rust的 layout 的细节。否则无法解析出正确的数据。 anchor在solana-program的基础上套了一层,最主要的就是解决数据编解码的问题。这个就是原生编码和 anchor 编码的差别。anchor 也是 solana官网推荐的写合约的方式。当然,如果你无畏 layout细节,native 合约也是可以的。 一些其他的推荐可以使用 just工具,预先写好合约编译,发布和测试命令。然后使用just运行。生成自己的流水线,是我个人推荐的方式。如果有一些其他复杂的命令行,可以再花 20 分钟学习 amber。可以设计出一些比较复杂的合约部署流程。丰俭由人。 教学视频https://www.youtube.com/watch?v=3GHlk6vosQw&list=PL53JxaGwWUqCr3xm4qvqbgpJ4Xbs4lCs7&index=12&ab_channel=Josh’sDevBox solana已经有较多的编程实践了,课程很多。可以看上面的视频进行学习。也是一个比较不错路径。","categories":[],"tags":[{"name":"合约","slug":"合约","permalink":"https://imzy.vip/tags/%E5%90%88%E7%BA%A6/"},{"name":"solana","slug":"solana","permalink":"https://imzy.vip/tags/solana/"}]},{"title":"REVM代码阅读 02","slug":"REVM代码阅读-02","date":"2024-05-28T20:23:51.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/44402/","link":"","permalink":"https://imzy.vip/posts/44402/","excerpt":"REVM代码阅读 02前文介绍在 01章节,对阅读 revm做了一些预先的准备工作,主要包括了解solidity语言,学习 OPCODES以及自顶向下了解 evm的基本结构三部分。其中第三部分最好大致实现征战虚拟机栈的运行过程加深理解。现在开始阅读 revm代码。阅读代码的过程比较枯燥,请保持耐心。 针对单独实现的 evm不在少数,使用 rust编写的 evm也不是唯一的。有下面的两个 https://github.com/rust-ethereum/evm 以上是 rust-ethereum名下的 evm项目,他是 parity名下的项目,在项目中提到 polkdot项目就使用该虚拟机。 另外一个就是我们看的 evm revm https://github.com/bluealloy/revm 也有众多的以太坊生态项目使用该虚拟机。该项目拥有一本 books来介绍虚拟机的设计思路。在初步阅读代码之前,我们先阅读文档来了解 revm的整体设计思路。 https://bluealloy.github.io/revm/","text":"REVM代码阅读 02前文介绍在 01章节,对阅读 revm做了一些预先的准备工作,主要包括了解solidity语言,学习 OPCODES以及自顶向下了解 evm的基本结构三部分。其中第三部分最好大致实现征战虚拟机栈的运行过程加深理解。现在开始阅读 revm代码。阅读代码的过程比较枯燥,请保持耐心。 针对单独实现的 evm不在少数,使用 rust编写的 evm也不是唯一的。有下面的两个 https://github.com/rust-ethereum/evm 以上是 rust-ethereum名下的 evm项目,他是 parity名下的项目,在项目中提到 polkdot项目就使用该虚拟机。 另外一个就是我们看的 evm revm https://github.com/bluealloy/revm 也有众多的以太坊生态项目使用该虚拟机。该项目拥有一本 books来介绍虚拟机的设计思路。在初步阅读代码之前,我们先阅读文档来了解 revm的整体设计思路。 https://bluealloy.github.io/revm/ revm是一个存粹的以太坊运行环境,没有任何网络以及共识的部分,也有降低简化阅读难度。 Revm BooksEvm根据 revm books的介绍,revm主要分四部分 revm: revm核心代码。 interpreter: 翻译为解释器。执行带有指令的循环。在上一节中已经提到,revm就是一个栈数据结构,依次弹出栈中的数据和指令(opcodes)进行和执行。 primitives: 以太坊的原始数据类型。比如U256。是对于 rust数据类型的包装。 precompile: 以太坊虚拟机预编译。在执行之前做一些数据的检查。 每个 crates内部都有也有很多细分模块,这里只是介绍 revm中主要的部分。 EVM部分。evm包含两大部分 Context和 handler. Context翻译为上下文,是一种编写二进制应用程序的常见方法,主要包含执行所需的状态。Handler包含作为执行逻辑的函数。Context分为两种具体的 EvmContext和 external context。external context翻译为外部 context。只要是为了外部调用执行的 Hook钩子。EvmContext为内部 Context,有database,environment,journaled state,precompiles四部分。 运行时 Runtime。包含从 handler中调用函数。参考以下示意图 所有的函数按照以上的四个类别进行分组,且按照对应的顺序运行。其中 Execution阶段包含两个循环。 call loop和 interpreter loop。 第一个循环是调用循环,循环中的每一步被称为 Frame,帧。Frame生成 Interpreter loop。Interpreter 负责遍历执行字节码操作(opcodes)。相当于在 call loop中嵌套 Interpreter loop。Interpreter 执行的结果是 InterpreterAction。InterpreterAction 相当于一个枚举,分别指示 Interpreter是否执行完成。传导到“父”级别 Frame中。 其中 Evm是负责运行的模块,在 Evm模块“之前”,有一个 EvmBuilder,会“设置”Evm需要执行的功能。Evmbuilder相当于一个“菜单”。 Interpreterevm是最核心的模块,也是程序的“启动点”。在上一小节中介绍的 evm中会开启两个两个循环,在 call loop中会嵌套执行 Interpreter循环。解释器循环直接涉及以太坊字节码操作,解释器直接充当事件循环逐步执行操作码。它设置 gas计算,合约本身,内存,栈堆操作,且返回执行结果。 Interprete’r crates中的 gas和 mememory简单介绍就是gas费计算,以太坊内存操作。大致介绍下其中的Host 部分: Host是一个 trait 因为 EVM操作是需要一定“链上信息的”,所以 Host中包含一些链上信息 env 包含当前区块和交易信息 load_account: 查询给定以太坊账户的信息 block_hash:检索给定区块号的哈希信息 balance code code_hash … 用于检索给定账号的余额 代码 代码 hash之类的信息 selfdestruct Host部分维护了一个线上数据获取的统一接口,可以使得虚拟机链接到不同的以太坊网络,比如 mainnet, 测试网等。所以这部分被命名为“主机”。 interpreter_actioninterpreter action主要定义了一些解释器执行过程中的数据结构。有 strcuct和 enum主要包含了evm操作的方方面面。比如调用和创建输入,调用上下文(context),价值转移(以太坊合约合约很多都是围绕转账展开的),以及自毁操作 其他结构略过。以上大致分散的介绍了revm中的前两部分。 待续。。 2024年05月28日20:23:10","categories":[],"tags":[{"name":"eve","slug":"eve","permalink":"https://imzy.vip/tags/eve/"}]},{"title":"REVM代码阅读 01","slug":"REVM代码阅读-01","date":"2024-05-15T14:51:16.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/44082/","link":"","permalink":"https://imzy.vip/posts/44082/","excerpt":"REVM代码阅读 01经过一些时间的准备,现在进行 REVM代码的阅读 准备环节这些准备主要包括一下的方面 速览 《精通以太坊》,有一个全局的认识 https://github.com/WTFAcademy/WTF-Solidity 通过这些资料来了解 solidity的语法。我的标准是学习完上面的课程并且完成习题,通过认证。 https://www.wtf.academy/docs/evm-opcodes-101 通过这些资料来学习以太坊的操作码 OPCODE, 因为不会 python, 所以使用 rust实现了其中其中的逻辑。可以参考这个链接https://github.com/TigerInYourDream/naive\\_evm 。主要需要跑通其中的用例(执行其中的 bytecode)。(至少做完 101 和 102) 做出了以上的准备之后,可以对以太坊虚拟机有一个整体性认识。其中改的代码可以参考上面的链接阅读。 总体概览经过以上的准备,对以太坊虚拟机有了一个感性的认识。从最简单的模型,以太坊虚拟机就是一个栈的数据结构,每次把b solidit编译成对应的 bytecode,然后拆分成一个又一个的 op code。把 op code按照栈的方式一次执行就完成了“虚拟机”的工作。 下面图来自于 WFT Academy的 op code 101教程,从高层次展示了以太坊虚拟机的结构","text":"REVM代码阅读 01经过一些时间的准备,现在进行 REVM代码的阅读 准备环节这些准备主要包括一下的方面 速览 《精通以太坊》,有一个全局的认识 https://github.com/WTFAcademy/WTF-Solidity 通过这些资料来了解 solidity的语法。我的标准是学习完上面的课程并且完成习题,通过认证。 https://www.wtf.academy/docs/evm-opcodes-101 通过这些资料来学习以太坊的操作码 OPCODE, 因为不会 python, 所以使用 rust实现了其中其中的逻辑。可以参考这个链接https://github.com/TigerInYourDream/naive\\_evm 。主要需要跑通其中的用例(执行其中的 bytecode)。(至少做完 101 和 102) 做出了以上的准备之后,可以对以太坊虚拟机有一个整体性认识。其中改的代码可以参考上面的链接阅读。 总体概览经过以上的准备,对以太坊虚拟机有了一个感性的认识。从最简单的模型,以太坊虚拟机就是一个栈的数据结构,每次把b solidit编译成对应的 bytecode,然后拆分成一个又一个的 op code。把 op code按照栈的方式一次执行就完成了“虚拟机”的工作。 下面图来自于 WFT Academy的 op code 101教程,从高层次展示了以太坊虚拟机的结构 核心概念概览 STACK 栈 以太坊虚拟机的核心概念就是一个栈。这里假设任何阅读本文的人都最基本的计算机知识。以太坊的栈每个元素长度为 256位(32bytes)。最大深度 1024。单次操作最多包含栈顶的 16个元素。超过最大元素限制会出现 “Stack too deep” 错误。 MEMORY 易失性存储,理解为一个动态数组。支持以 8 为或者 256 位写入,以 256 位读取。 Storage 区别于 memory,Storage为持久化存储。简单理解存储为一个键值对HashMap。因为计算机实际上的存储只有数组。实际上的存储是目标都是如何高效生成 key,高效查找 value。区块链存储是一个比较复杂的话题,超过了文章的讨论范畴。 对于以太坊的存储,键和值都是 256bit的数据。支持以 256 bit的读和写。他的数据是存储在链上的,持久化存在的。存储数据是一个比较昂贵的操作,读和取都需要消耗 gas费用。在需要阅读的 revm代码中,它借助 ethers-provider存储,自己并不直接实现存储的部分。 GAS gas就是燃料费。以太坊用燃料费来衡量合约的消耗。revm中有很多代码是用来计算 gas费用的。一笔交易的 GAS总消耗是所有 OP_CODE gas费用之和。注意你需要合理的估算 gas费用。如果 gas不足,合约会在 gas消耗完之后停止合约执行,gas费用不会返回。优化gas消耗是合约优化的重要方向。 以太坊执行模型以太坊执行概览图如下图所示,这个也是参考了WTF 当交易准备执行的时候,主要执行下面的步骤 初始化执行环境,并且加载字节码。执行环境被称为 ENV在后续解读 REVM源码的部分会看到。 字节码会被转化为 opcodes,逐一执行。每个 Opcodes代码一种固定的操作。这个可以参考以太坊 Opcodes章节 执行一个opcodes,对应需要相当量的 gas费用。如果 gas消耗完毕,则合约中断执行,在 gas消耗费用以外的数据逐一回滚。 执行完成后,所有数据会在区块链上记录。包括 gas消耗和日志信息。(即Solidity中的 Events) 从顶层理解以太坊虚拟机的结构后,可以参考下面的代码 https://github.com/TigerInYourDream/naive_evm 这个库是根据以太坊虚拟机的最基本原理实现的以太坊Opcodes操作实例,相当于参考 WTF academic 中的代码,但是把它实现为 RUST。 原文是 python实现的。 在以上的执行模型中,相当于展示了 Opcodes每个操作的具体意义。有了以上的基础准备之后,下一篇开始阅读 REVM源码。 2024-05-15 14:56:58","categories":[],"tags":[{"name":"-evm","slug":"evm","permalink":"https://imzy.vip/tags/evm/"}]},{"title":"diesel使用小要点","slug":"diesel使用小要点","date":"2021-10-14T18:39:14.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/35993/","link":"","permalink":"https://imzy.vip/posts/35993/","excerpt":"diesel使用的一些小知识点最近因为使用diesel,大致看了diesel的文档同时结合自己两天内写代码的经验,总结出下面几条要点,作为参考。严格来说,这些知识点比较琐碎不能够成为一篇文章,但是想到可以为自己以后使用diesel作参考还是写出来。 设置好.env之后 diesel setup 是创建数据库的 diesel migration generate create_posts 最后一个参数是创建migration的文件夹名的 它会生成一个带时间的migration文件 里面有个up和down的sql up 里面负责创建数据库表的 down 里面负责撤回车操作的 up 和 down 里面的sql要自己创建 diesel migration run 执行migration里面的up.sql的操作的 确切的说就是建表 5. diesel migration redo 执行migration里面的down.sql的操作 确切的拉说是删除表 注意了 setup是会创建数据库和直接运行migration run中的建表数据的 但是如果数据库存在是不会运行的。如果在 migration中添加,后续这个也是不会运行的 这一步进行之后schema文件也会生成。这个文件是可以改的 ,但是不建议改 如果升级数据库,请使用新的migration文件 注意使用extern crate diesel 和#[marco use],不然schema下的东西又是找不到的 以上的条目中需要补充的还有如下 本来以为在rust2018版本以后,就完全不使用extern crate了。但是事实证明还是需要extern crate diesel和#[marco_use]的,不然会有很多东西找不到 使用diesel尽量引入prelude::* 虽然我不喜欢这样引入,但是自己精确引入会很麻烦 一定要注意schema文件 如果使用只是使用diesel查询和写入如何简化代码这个问题会显的很奇怪,因为作为一个ORM,本来就是辅助我们进行增删改查的。其实我们看了diesel的官方指引之后就会觉得这个问题很合理。重新表述如下 根据diesel的官方指引,我们是用建立连接,建立数据库,设计表这几个步骤一路走下来的。实际上,大多数时候,数据表并不是我们建立的。我们只需要使用diesel的库,连接数据库CRUD即可。这种情况下,如何简化操作呢? 根据我的尝试大致需要如下 引入库,这个是必须的 设置sql路径,这个是必须的 无需diesel setup因为我们不需要建立数据库,数据库是存在的 无需diesle migration run 因为表也不是我们建立的。 没有建表,没运行diesel migration run,则diesel不会为我们自动建立schema.rs文件,也不会在schema.rs中生成table宏的语法。所以我们需要手动去建立schema.rs的文件,src/schema.rs。就是必须建立的在src路径下面。这样才不会出错。然后我们在schema.rs中手动写入 table!宏。这样才可以正常使用。 以上的步骤是可以正常使用的,也是文件最少的情况。还有下面的情况补充 如果我们运行了diesel set 如果数据库存在,是不会建立数据库的。会产生一个migrations文件。一个和Cargo.toml同级别的diesel.toml文件,里面配置了schema.rs文件的位置。 所以上一步中如果不运行diesel set,需要我们手动把schema文件放在指定的位置。 同时,因为我们 不建表,所以不运行diesel migration run。schema里面是空的,为了正确使用orm的方便特性,我们得去手动补全这个文件,自己写table!宏。当然这个很容易。看example即可 一些零碎的知识,其他的正常使用参考文档即可。这个算是在文档之外的补充备查!","text":"diesel使用的一些小知识点最近因为使用diesel,大致看了diesel的文档同时结合自己两天内写代码的经验,总结出下面几条要点,作为参考。严格来说,这些知识点比较琐碎不能够成为一篇文章,但是想到可以为自己以后使用diesel作参考还是写出来。 设置好.env之后 diesel setup 是创建数据库的 diesel migration generate create_posts 最后一个参数是创建migration的文件夹名的 它会生成一个带时间的migration文件 里面有个up和down的sql up 里面负责创建数据库表的 down 里面负责撤回车操作的 up 和 down 里面的sql要自己创建 diesel migration run 执行migration里面的up.sql的操作的 确切的说就是建表 5. diesel migration redo 执行migration里面的down.sql的操作 确切的拉说是删除表 注意了 setup是会创建数据库和直接运行migration run中的建表数据的 但是如果数据库存在是不会运行的。如果在 migration中添加,后续这个也是不会运行的 这一步进行之后schema文件也会生成。这个文件是可以改的 ,但是不建议改 如果升级数据库,请使用新的migration文件 注意使用extern crate diesel 和#[marco use],不然schema下的东西又是找不到的 以上的条目中需要补充的还有如下 本来以为在rust2018版本以后,就完全不使用extern crate了。但是事实证明还是需要extern crate diesel和#[marco_use]的,不然会有很多东西找不到 使用diesel尽量引入prelude::* 虽然我不喜欢这样引入,但是自己精确引入会很麻烦 一定要注意schema文件 如果使用只是使用diesel查询和写入如何简化代码这个问题会显的很奇怪,因为作为一个ORM,本来就是辅助我们进行增删改查的。其实我们看了diesel的官方指引之后就会觉得这个问题很合理。重新表述如下 根据diesel的官方指引,我们是用建立连接,建立数据库,设计表这几个步骤一路走下来的。实际上,大多数时候,数据表并不是我们建立的。我们只需要使用diesel的库,连接数据库CRUD即可。这种情况下,如何简化操作呢? 根据我的尝试大致需要如下 引入库,这个是必须的 设置sql路径,这个是必须的 无需diesel setup因为我们不需要建立数据库,数据库是存在的 无需diesle migration run 因为表也不是我们建立的。 没有建表,没运行diesel migration run,则diesel不会为我们自动建立schema.rs文件,也不会在schema.rs中生成table宏的语法。所以我们需要手动去建立schema.rs的文件,src/schema.rs。就是必须建立的在src路径下面。这样才不会出错。然后我们在schema.rs中手动写入 table!宏。这样才可以正常使用。 以上的步骤是可以正常使用的,也是文件最少的情况。还有下面的情况补充 如果我们运行了diesel set 如果数据库存在,是不会建立数据库的。会产生一个migrations文件。一个和Cargo.toml同级别的diesel.toml文件,里面配置了schema.rs文件的位置。 所以上一步中如果不运行diesel set,需要我们手动把schema文件放在指定的位置。 同时,因为我们 不建表,所以不运行diesel migration run。schema里面是空的,为了正确使用orm的方便特性,我们得去手动补全这个文件,自己写table!宏。当然这个很容易。看example即可 一些零碎的知识,其他的正常使用参考文档即可。这个算是在文档之外的补充备查!","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/tags/rust/"}]},{"title":"占星与星座计算 morinus house system","slug":"占星与星座计算-morinus-house-system","date":"2021-08-10T22:37:53.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/26767/","link":"","permalink":"https://imzy.vip/posts/26767/","excerpt":"占星与星座计算 morinus house system在系列前三节,已经基本介绍了和占星有关的模型,基本的分宫制的原理和占星以及天文中所出现的术语,这一节将带大家进行一次实际的占星计算。本文仅涉及星盘的计算,不涉及其他。 本文的重点是星盘的计算。本文将采用莫林分宫制morinus house system!至于为什么选择莫林分宫制,是因为莫林分宫制可以找到具体的数学计算公式,便于计算。且莫林分宫制在高纬度地区受到的扭曲小。至于分宫制的基本原理,可以查看上一节分宫制基本原理。 对于“morinus house system”是有专门的计算方法,通过一系列的加减乘除算出一个人出生时的MC、Asc以及每个宫头所在得度数,就可以排出一个近乎完整的星图了。 ASC:东升点是在诞生时刻在诞生地点的东面地平与黄道交接的一点 MC:天顶 我们在计算的时候需要以下参数 当事人的出生位置,需要从具体地点转化成经纬度 出生时间,需要转化为恒星时 黄赤交角,这个是固定值。关于黄赤交角可以看第一节天球部分。23°27′ 以及如下公式 MC = arctan(tan(RAMC) / cos(e)) RAMC为上中天的赤经度数","text":"占星与星座计算 morinus house system在系列前三节,已经基本介绍了和占星有关的模型,基本的分宫制的原理和占星以及天文中所出现的术语,这一节将带大家进行一次实际的占星计算。本文仅涉及星盘的计算,不涉及其他。 本文的重点是星盘的计算。本文将采用莫林分宫制morinus house system!至于为什么选择莫林分宫制,是因为莫林分宫制可以找到具体的数学计算公式,便于计算。且莫林分宫制在高纬度地区受到的扭曲小。至于分宫制的基本原理,可以查看上一节分宫制基本原理。 对于“morinus house system”是有专门的计算方法,通过一系列的加减乘除算出一个人出生时的MC、Asc以及每个宫头所在得度数,就可以排出一个近乎完整的星图了。 ASC:东升点是在诞生时刻在诞生地点的东面地平与黄道交接的一点 MC:天顶 我们在计算的时候需要以下参数 当事人的出生位置,需要从具体地点转化成经纬度 出生时间,需要转化为恒星时 黄赤交角,这个是固定值。关于黄赤交角可以看第一节天球部分。23°27′ 以及如下公式 MC = arctan(tan(RAMC) / cos(e)) RAMC为上中天的赤经度数 星表上专门有栏目是星星的赤经(希腊字母α)的一般格式是XhXXminXXs.其实概念和地球的东经西经差不多,但不分东西两个方向,就是从0度开始按一定方向到360度(就是开始的地方),更简单了.关于换算,先从角度上看赤经是在天球赤道自西向东由0小时至24 小时。把赤经的一周360度划成24份,一份就是15度.那么很简单了,1小时=15度.时间上的小时和角度上的度的下面的小单位都叫分,秒,换算也都是60.为了不混淆,用时分,时秒和角分,角秒来表示.根据那个原始的换算公式,可以得到1时分=15角分,1时秒=15角秒 。赤经计算的起点为春分点,春分点是太阳在每年的春分(3月21日前后)所处的位置。 注1:所有的数字都要转化为时间 注2:RAMC需要恒星时间换算。 做了概念的准备之后,我们开始选取一个具体的例子进行计算 首先,假设当事人出生得地理位置为(52o 13′ N and 6o 54′ E),荷兰恩斯科德。时间:2016年11月2日(公历),21:17:30(格林尼治时间),恒星时:0:35:23.6,黄赤交角e:23°27′。 想知道什么是恒星时:点这个 然后把数据代入儒略历和恒星时计算准备的程序中。得到结果 res 1.9485282441601157°The sidereal hour is 0.1299018829440077hThe sidereal time is 0.0h7.0m47.646778598427716s 得到当时的恒星时为1.95度,转换成时间为0.1299小时。0时7分47秒。 这里的度数就是RAMC 为1.95. 黄赤交角e=23度27分,转化为时间为23.45时(27/60=0.45) MC= arctan ( TAN (1.95) ÷ COS 23.45 ) 计算代码使用rust自带的三角函数,rust中的三角使用的是弧度,我们需要在弧度和角度之间互相转化。 得到中天数为2.12度。根据之前分宫制的计算,三十度为一个宫位。2.12度为一个首宫位白羊座。所以我们可以这样说:MC在白羊座2.12处。 接下来我们计算上升点ASC ASC= ARCCOT (- ( (TAN f x SIN e) + (SIN RAMC x COS e) ) ÷ COS RAMC) 因为计算机上没有反余切函数,我们需要换算: ARCCOT = (π/2) - ARCTAN,这是弧度,角度是90度- ARCTAN。 所以公式可以转换为下面 ARCTAN(- ( (TAN f x SIN e) + (SIN RAMC x COS e) ) ÷ COS RAMC) 其中f为出生纬度52.21时,已换算. 最终的结果为 119.28330880864735 进入第四宫位的末尾119.2833 - 30*3 = 29.283308808647348度。即便是巨蟹座的29.28度。巨蟹座的尾巴。 123456789101112131415161718192021222324252627use std::f64::consts::PI;use std::f64::consts::E;pub fn to_radians(x: f64) -> f64 { x * PI / 180f64}pub fn to_angle(x: f64) -> f64 { x * 180f64 / PI}pub fn houses_system() { println!("houses_system!"); let a = to_radians(1.95f64).tan() / to_radians(23.45f64).cos(); let mc = a.atan(); println!("{:?}", to_angle(mc)); //ARCTAN(- ( (TAN f x SIN e) + (SIN RAMC x COS e) ) ÷ COS RAMC) let f = -to_radians(52.21f64).tan() * E.sin() + to_radians(1.95f64).sin() * E.cos(); println!("{:?}", f); let mut asc = to_angle(f.atan()); println!("asc {:?}", asc); if asc < 0f64 { asc = 90f64 - asc } println!("asc {:?}", asc);} 具体的代码还是在上一节的代码中。为了清晰,改变了代码结构。因为展示的是计算过程,代码风格较差。 https://github.com/TigerInYourDream/sidereal-time 对应的相应的各种占星计算也是根据如上的公式进行一步步计算和推演得出的。本文和一些成熟的星盘数据进行了对比,发现有一定的角度偏差。发现主要的差别在恒星时的换算上。但是误差较小,最终导致了最后的ASC上升星座偏差了三度。因为本文查看了相对应的天文论文,对占星的具体算法也有一定争议。需要进一步讨论这个偏差。不过也看出来在古代占星的计算和推演相当的繁复,借助现代计算机对这种计算有了很好的辅助作用。本系列文章只谈论占星中涉及的具体的天文和星盘宫位的计算。 永远仰望天空,永远保持旺盛的好奇心!","categories":[{"name":"天文学","slug":"天文学","permalink":"https://imzy.vip/categories/%E5%A4%A9%E6%96%87%E5%AD%A6/"}],"tags":[{"name":"占星","slug":"占星","permalink":"https://imzy.vip/tags/%E5%8D%A0%E6%98%9F/"}]},{"title":"占星与星座计算 儒略历和恒星时计算","slug":"占星与星座计算-儒略历和恒星时计算","date":"2021-08-07T22:35:15.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/3102/","link":"","permalink":"https://imzy.vip/posts/3102/","excerpt":"占星与星座计算 儒略历和恒星时计算在前两节的文章基础之上,我们开始进行真正的计算。在介绍计算星盘之前,我们需要一个预先准备的数据。儒略历和恒星时。网上的现成代码似乎没有说的很清楚,公式不够统一,结果也难以验证,为此参考了很多资料。其中有两个资料确定是对的。 https://zh.wikipedia.org/wiki/恒星时 上面公式中关于儒略历的转化公式是正确的,请注意其中的高斯符号。高斯符号相当于代码中的floor()函数。 后面关于恒星时的计算不确定正确性。 然后参考一份关于天文算法的论文,或者是一本翻译的PDF。感谢“冯剑和他的译友”翻译了这本天文算法,使我得到了确切的公式。 关于什么是恒星时可以也可以参考上面的wiki。 关于结果,分别得到了多项式T,算出来以角度为单位的恒星时,以时间hour为单位的恒星时,以时分秒为单位的恒星时。 现在公历纪元的年表示为Y、月为M、日为D、时为h、分为m、秒为s,1月、2月分别当做上一年的13月、14月。(例:2010年1月1日时Y=2009, M=13, D=1),然后求出儒略日 计算儒略历请注意以上的重点。","text":"占星与星座计算 儒略历和恒星时计算在前两节的文章基础之上,我们开始进行真正的计算。在介绍计算星盘之前,我们需要一个预先准备的数据。儒略历和恒星时。网上的现成代码似乎没有说的很清楚,公式不够统一,结果也难以验证,为此参考了很多资料。其中有两个资料确定是对的。 https://zh.wikipedia.org/wiki/恒星时 上面公式中关于儒略历的转化公式是正确的,请注意其中的高斯符号。高斯符号相当于代码中的floor()函数。 后面关于恒星时的计算不确定正确性。 然后参考一份关于天文算法的论文,或者是一本翻译的PDF。感谢“冯剑和他的译友”翻译了这本天文算法,使我得到了确切的公式。 关于什么是恒星时可以也可以参考上面的wiki。 关于结果,分别得到了多项式T,算出来以角度为单位的恒星时,以时间hour为单位的恒星时,以时分秒为单位的恒星时。 现在公历纪元的年表示为Y、月为M、日为D、时为h、分为m、秒为s,1月、2月分别当做上一年的13月、14月。(例:2010年1月1日时Y=2009, M=13, D=1),然后求出儒略日 计算儒略历请注意以上的重点。 代码如下 1234567891011121314151617181920212223242526272829303132333435363738394041424344use chrono::{Utc, TimeZone, FixedOffset, Datelike, Timelike};use num::traits::pow;fn main() { println!("sidereal time!"); // let birthday = Utc.ymd(1987, 4, 10).and_hms(19, 21, 00); let birthday = Utc.ymd(2016, 11, 2).and_hms(21, 17, 30); let timezone = FixedOffset::east(0); birthday.with_timezone(&timezone); let mut year = birthday.year() as f64; let mut month = birthday.month() as f64; let day = birthday.day() as f64; let hour = birthday.hour() as f64; let minute = birthday.minute() as f64; let second = birthday.second() as f64; if month < 2f64 { year = year - 1f64; month = month + 12f64; } let jd = (&365.25 * year).floor() + (year / 400f64).floor() - (&year / 100f64).floor() + (30.59 * (month - 2f64)).floor() + day + 1721088.5 + hour / 24f64 + minute / 1440f64 + second / 86400f64; println!("jd: {:?}", jd); let t = (jd - 2451545.0) / 36525f64; println!("t: {:?}", t); let g = 280.46061837 + 360.98564736629 * (jd - 2451545.0) + 0.000387933 * pow(t, 2) - pow(t, 3) / 38710000f64; println!("g: {:?}", g); let mut res = g % 360f64; if res < 0f64 { res = 360f64 + res; } println!("res {:?}°", res); res = res / 15f64; println!("The sidereal hour is {:?}h", res); let hour = res.floor(); let minute = ((res - hour) * 60f64).floor(); let second = (((res - hour) * 60f64) - minute) * 60f64; println!("The sidereal time is {:?}h{:?}m{:?}s", hour, minute, second);} 其中jd代表儒略历时间。 儒略历的计算结果可以对比 https://www.aavso.org/jd-calculator 可以确定计算是一致的,也就验证了wiki中的公式的正确。后半部分则是引用了论文中的公式,计算结果和示例推导过程一致。所以计算结果可以确定是对的。顺便吐槽某个占星网站的计算,其中恒星时是错误的,伪学不靠谱啊。 关于代码:代码使用rust,使用了chrono库表示时间。也使用了num,不过似乎也没用到什么特性,只用了一个pow。为了保证精度,所有数字全部使用f64 另外本期计算的是平恒星时,非视恒星时。不考虑赤经章动修正。 代码在github已上传。 https://github.com/TigerInYourDream/sidereal-time","categories":[],"tags":[{"name":"-rust -星座","slug":"rust-星座","permalink":"https://imzy.vip/tags/rust-%E6%98%9F%E5%BA%A7/"}]},{"title":"占星与星座计算 黄道十二宫与分宫原理","slug":"占星与星座计算-黄道十二宫与分宫原理","date":"2021-08-06T20:29:32.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/13704/","link":"","permalink":"https://imzy.vip/posts/13704/","excerpt":"占星与星座计算 黄道十二宫与分宫原理在占星与星座计算的第一篇文章中,尽可能详细的介绍了天球的构建过程原理和相关的概念。这一节我来介绍何为黄道12宫以及分工制的基本原理,不涉及具体分宫制。 黄道带上一节的天球示意图中,我们已经标注出来了黄道,就是假定地球不动,太阳绕地球运动所形成的面,就是黄道。因为地球自转和地球公转是有一定角度偏差的。所以黄道和赤道是有一个23.5度的夹角的。 黄道带(或是黄道十二宫)的概念起源于巴比伦占星术,巴比伦人注意到了与太阳同时升起的星星,在黎明之前,可以观察到靠近太阳位置的星星升起,这些星星以一个似乎规则的圆周来回运动。他们将这些星星分为十二组,并给其命名。希腊人从巴比伦人那里继承了这一习惯,才有了黄道十二宫,这个词来源于希腊文zodion,意为“小动物”。 具体的黄道带的概念如下 黄道带(希腊语:ζῳδιακός, zōdiakos),是天文学的名词,指的是在黄道上的星座组成的环带,不仅是太阳每年在天球上所行经的路径,月球和行星的路径也大略都在黄道的附近,因此也全部都在黄道带的星座内。 在占星术,黄道带被人为划分为十二个随中气点移动(与实际星座位置不一致)的均等区域,各自都有符号。 因此,黄道带是一个天球坐标系统,或是更具体的说是一个黄道坐标系统,以黄道做为纬度的基准平面(原点),并且以太阳在春分时的位置作为经度的原点 黄道12宫的顺序如下 白羊宫(Aries, ♈︎);金牛宫(Taurus, ♉︎);双子宫(Gemini, ♊︎);巨蟹宫(Cancer, ♋︎);狮子宫(Leo, ♌︎);处女宫(Virgo, ♍︎);天秤宫(Libra, ♎︎);天蝎宫(Scorpio, ♏︎);射手宫(Sagittarius, ♐︎);摩羯宫(Capricornus, ♑︎);水瓶宫(Aquarius, ♒︎);双鱼宫(Pisces, ♓︎)。 古人观察星空,发现天体分作两类:一类固定在天球上,组成各个星座,形成一幅永恒的天空背景,称之为恒星;另一类天体在黄道附近运行,不断穿过黄道上的十二个星座(或中国的二十八宿),称之为行星。这些行星包括七颗(七曜),分别是阴阳——太阳和太阴(月球),以及五行——金木水火土五个肉眼可见的经典行星。太阳在黄道上一年运行一圈,太阴在黄道上一个月运行一圈。一定要注意,占星中的行星和实际的行星概念并不一致,这毕竟是古代天文学。由于整体一环为360度,所以每个星座占据了360/12 = 30 度。","text":"占星与星座计算 黄道十二宫与分宫原理在占星与星座计算的第一篇文章中,尽可能详细的介绍了天球的构建过程原理和相关的概念。这一节我来介绍何为黄道12宫以及分工制的基本原理,不涉及具体分宫制。 黄道带上一节的天球示意图中,我们已经标注出来了黄道,就是假定地球不动,太阳绕地球运动所形成的面,就是黄道。因为地球自转和地球公转是有一定角度偏差的。所以黄道和赤道是有一个23.5度的夹角的。 黄道带(或是黄道十二宫)的概念起源于巴比伦占星术,巴比伦人注意到了与太阳同时升起的星星,在黎明之前,可以观察到靠近太阳位置的星星升起,这些星星以一个似乎规则的圆周来回运动。他们将这些星星分为十二组,并给其命名。希腊人从巴比伦人那里继承了这一习惯,才有了黄道十二宫,这个词来源于希腊文zodion,意为“小动物”。 具体的黄道带的概念如下 黄道带(希腊语:ζῳδιακός, zōdiakos),是天文学的名词,指的是在黄道上的星座组成的环带,不仅是太阳每年在天球上所行经的路径,月球和行星的路径也大略都在黄道的附近,因此也全部都在黄道带的星座内。 在占星术,黄道带被人为划分为十二个随中气点移动(与实际星座位置不一致)的均等区域,各自都有符号。 因此,黄道带是一个天球坐标系统,或是更具体的说是一个黄道坐标系统,以黄道做为纬度的基准平面(原点),并且以太阳在春分时的位置作为经度的原点 黄道12宫的顺序如下 白羊宫(Aries, ♈︎);金牛宫(Taurus, ♉︎);双子宫(Gemini, ♊︎);巨蟹宫(Cancer, ♋︎);狮子宫(Leo, ♌︎);处女宫(Virgo, ♍︎);天秤宫(Libra, ♎︎);天蝎宫(Scorpio, ♏︎);射手宫(Sagittarius, ♐︎);摩羯宫(Capricornus, ♑︎);水瓶宫(Aquarius, ♒︎);双鱼宫(Pisces, ♓︎)。 古人观察星空,发现天体分作两类:一类固定在天球上,组成各个星座,形成一幅永恒的天空背景,称之为恒星;另一类天体在黄道附近运行,不断穿过黄道上的十二个星座(或中国的二十八宿),称之为行星。这些行星包括七颗(七曜),分别是阴阳——太阳和太阴(月球),以及五行——金木水火土五个肉眼可见的经典行星。太阳在黄道上一年运行一圈,太阴在黄道上一个月运行一圈。一定要注意,占星中的行星和实际的行星概念并不一致,这毕竟是古代天文学。由于整体一环为360度,所以每个星座占据了360/12 = 30 度。 现在根据上面这些背景知识,我们来理解一个说法,“太阳进入白羊座23度“,作何解,白羊座是星座起点,那对应的角度就是经度23度。 “月亮进入天蝎座7度”:天蝎为第八个星座组。30*7+7 = 217。即便经度217度。 值得注意的是蛇夫座⛎,也被黄道经过。但是蛇夫座并不属于占星中的黄道十二(三)宫。所以你应该能理解某些作品中不存在的黄道12宫中为什么会出现蛇夫座了吧。根据现代天文学黄道中有13个星座。 分宫制介绍了黄道带的概念之后,我们知道了一个事实:观测星空一整年,即太阳围绕地球一圈,在不同的时候依次在黎明的太阳附近看到12个星系,称为黄道12宫。但是现在有个问题,人并不是地球本身,受到人出生地点经纬度影响,怎么做出一个地面天球坐标系统,确定人出生的星座问题。 下面介绍分宫制的基本原理: 分宫制的计算方法是,根据诞生时刻和地点经纬度,计算东升点(Ascedent)及天顶(MC, Medium Coel)。东升点是在诞生时刻在诞生地点的东面地平与黄道交接的一点。相对於东升点,在西方地平交接黄道的一点就是日没点(Dsc, Dscendant)。此两点连接起来,代表诞生时刻的地平线。地平线以上是日间可见的星象,地平线以下是夜间可见的星象。天顶即是当时黄道在天球最高的一点;相对於天顶,黄道在天球最低的一点为天底(IC,Imum Coeli)。计算出东升点後,以东升点的定位,将当时天球分成12份,利用投影法或其他数学计算投射到黄道之上,那就是星盘上后天12宫(Houses)的分配情况。 其本质就是如何确定坐标系的起点和方位。 因为本文不是讲占星历史的,所以并不会考据占星历史上的分宫制的不同流派。但是所有的分宫制都是依据以上的基本原理进行划分的。但是全部都是以东升点和天定做定位的。","categories":[{"name":"天文","slug":"天文","permalink":"https://imzy.vip/categories/%E5%A4%A9%E6%96%87/"}],"tags":[{"name":"占星","slug":"占星","permalink":"https://imzy.vip/tags/%E5%8D%A0%E6%98%9F/"}]},{"title":"占星与星座计算 天球","slug":"占星与星座计算-天球","date":"2021-08-04T18:49:23.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/22991/","link":"","permalink":"https://imzy.vip/posts/22991/","excerpt":"占星与星座计算 天球最近因为某些原因,有了一段空余的时间。当你空闲便有了一些奇思妙想。因为很多年前特别喜欢一个关于星座的故事,所以利用这一段空余时间写一个系列文章讲占星中的一个小知识。为了不使文章引起争议,特此声明:本人完全相信星座!不解释。文章的前面系列介绍和占星有关的天文基本概念,后面具体根据人的出生时间,地理位置(经纬度)来计算人的星座。可能读了这一系列的文章,大家可以更好的理解为什么星座归类于“占星学”,这样一个听起来非常正式的名字。另外因为不是撰写论文,本文不会详细列出各类依据。太累! 天球概念 以上图片为完整的天球示意图 和地球概念相对,古代天文学家提出了天球的概念。天球是一个想想的球体,他也是旋转的。理论上具有无限大的半径且和地心是同心的。如图所示:和地球有赤道和南北极的概念对应,天球也有对应的天球赤道和天球南北极。 古代天文学家认为我们和星体是等距离的,尽管这并不正确。因为古代缺乏有效的天文观测手段。星球不是靠我们肉眼就可以判断出距离的。古代科学家克服了这种距离上的因素,使用角度来确定星球在天空中的位置。在当时观测条件受限的情况下,不失为一种很有效的天文抽象系统。 地球会围绕地轴自转,相对应于地球不动的参照系下。天球也会围着天极旋转,24小时不停。这就形成了古代天文学家的眼中的昼夜交替原理。太阳,卫星,形星都是东升西落的,这种称为天球的周日实视运动。地球因为有其公转,一颗横行总是比前一天提前(约4min)上升。 和地球对应的概念一样,天球也有自己的南北半球,对应也有南北回归线,南北极 北回归线:北回归线是太阳在北半球能够垂直射到的离赤道最远的位置点,将该点之纬度线便叫“北回归线”,在公元2018年,其位置约在北纬23°26′11.3″ (或 23.43648°)。相反,南纬23°26′11.3″ (或 23.43648°)则为“南回归线”。","text":"占星与星座计算 天球最近因为某些原因,有了一段空余的时间。当你空闲便有了一些奇思妙想。因为很多年前特别喜欢一个关于星座的故事,所以利用这一段空余时间写一个系列文章讲占星中的一个小知识。为了不使文章引起争议,特此声明:本人完全相信星座!不解释。文章的前面系列介绍和占星有关的天文基本概念,后面具体根据人的出生时间,地理位置(经纬度)来计算人的星座。可能读了这一系列的文章,大家可以更好的理解为什么星座归类于“占星学”,这样一个听起来非常正式的名字。另外因为不是撰写论文,本文不会详细列出各类依据。太累! 天球概念 以上图片为完整的天球示意图 和地球概念相对,古代天文学家提出了天球的概念。天球是一个想想的球体,他也是旋转的。理论上具有无限大的半径且和地心是同心的。如图所示:和地球有赤道和南北极的概念对应,天球也有对应的天球赤道和天球南北极。 古代天文学家认为我们和星体是等距离的,尽管这并不正确。因为古代缺乏有效的天文观测手段。星球不是靠我们肉眼就可以判断出距离的。古代科学家克服了这种距离上的因素,使用角度来确定星球在天空中的位置。在当时观测条件受限的情况下,不失为一种很有效的天文抽象系统。 地球会围绕地轴自转,相对应于地球不动的参照系下。天球也会围着天极旋转,24小时不停。这就形成了古代天文学家的眼中的昼夜交替原理。太阳,卫星,形星都是东升西落的,这种称为天球的周日实视运动。地球因为有其公转,一颗横行总是比前一天提前(约4min)上升。 和地球对应的概念一样,天球也有自己的南北半球,对应也有南北回归线,南北极 北回归线:北回归线是太阳在北半球能够垂直射到的离赤道最远的位置点,将该点之纬度线便叫“北回归线”,在公元2018年,其位置约在北纬23°26′11.3″ (或 23.43648°)。相反,南纬23°26′11.3″ (或 23.43648°)则为“南回归线”。 因为地球是自西向东旋转,所以就把天球当做“自东向西旋转”。 以天球为基本概念来量化分析天体的朝向。有了天球这个基本抽象,天文学家开始创造不一样的天体坐标系,比如黄道系,银道系。需要注意的是,天球仪是在天球外看天球。天文馆的模拟器是还在天球内看天球。所以这是两个看起来不一样的天球系统。 黄道坐标系:黄道是由地球上观察太阳一年中在天球上的视运动所通过的路径,若以地球“不动”作参照的话就是太阳绕地球公转的轨道平面(黄道面)在天球上的投影。黄道与天赤道相交于两点:春分点与秋分点(这两点称二分点);而黄道对应的两个几何极是北黄极(在天龙座)、与南黄极 (在剑鱼座)。在黄道上与黄道平行的小圆称黄纬,符号β,以由黄道面向北黄极方向为正值(0°至90°),向南黄极方向则为负值。垂直黄道的经度称黄经,符号为λ,由春分点起由西向东量度(0°至360°)。像赤道坐标系中的赤经一样,以春分点做为黄经的起点。 注:本文不讨论地球进动现象。 黄道面和春秋分点已经在示意图中标注出来。 银道坐标系,是以太阳为中心,并且以银河系明显排列群星的平面为基准的天球坐标系统,它的“赤道”是银河平面。相似于地理坐标,银道坐标系的位置也有经度和纬度。 根据观测者位置的不同,有不同的天球中心。在地面上观测,观测者的眼睛就是天球中心。这样建立的天球就是地面天球。 如果从地心观测,则叫做地心天球。把地轴无限延长,就是假想的天轴,地球北极指的一点是北天极,地球南极指的一点就是南天极。通过地球中心和天轴垂直的平面叫做天赤道面,天赤道面和天球的会合处就是天赤道。 太阳在天球上每天移动约1度,一年则移动一周,这称之为太阳周年视运动,太阳中心在天球上视运动的轨迹则是黄道。 天赤道(celestial equator)是天球上假象的一个大圈,位于地球赤道的正上方;也可以说是垂直于地球地轴把天球平分成南北两半的大圆,理论上有无限长的半径。相对于黄道面,天赤道倾斜23.5°,是地轴倾斜的结果。当太阳在天赤道上时,白昼和黑夜到处都相等,因此天赤道也被称为昼夜中分线(equinoctial line)或昼夜平分圆;那时北半球和南半球都处于春分或者秋分。在一年当中,太阳有两次机会处于天赤道上。只要我们把地球赤道不断向外扩大,一直延伸到无限大,这个无限的圆就是天赤道。","categories":[{"name":"天文","slug":"天文","permalink":"https://imzy.vip/categories/%E5%A4%A9%E6%96%87/"}],"tags":[{"name":"占星","slug":"占星","permalink":"https://imzy.vip/tags/%E5%8D%A0%E6%98%9F/"}]},{"title":"比特币pow难度验证","slug":"比特币pow难度验证","date":"2021-06-19T15:58:46.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/58506/","link":"","permalink":"https://imzy.vip/posts/58506/","excerpt":"比特币pow难度验证何为POWpow的全称为proof of work,即工作量证明。简单的解释为“做了多少工作”。抛开区块链的背景,pow就是对自己做了多少工作的一种说明:比如做了学习了50个小时的汽车驾驶。而他人很容易验证这个结果:你可能50个小时之后拿到了一本驾照。别人就知道你确实在学习驾驶上使用了50个小时。 Block Header在区块链的世界里,pow的数据可以体现在区块链的区块头中。当然一般来说,讲解POW的难度离不开挖矿问题。本文因为主要讨论方向的问题,不展开讲挖矿,主要从区块头入手。在阅读下面的内容之前,默认读者已经有了如下前置知识 区块链常识 比特币基本概念 挖矿基本概念 抛开前置知识之后,我们来看区块头的数据结构。 https://en.bitcoin.it/wiki/Protocol_documentation#Block_Headers 可以直接参考以上链接,当然可以可以直接查看比特币的源码,我们现在把数据列出来。 12345678struct header_structure { // BYTES NAME uint32_t nVersion; // 4 version uint8_t hashPrevBlock[32]; // 32 previous block header hash uint8_t hashMerkleRoot[32]; // 32 merkle root hash uint32_t nTime; // 4 time uint32_t nBits; // 4 target uint32_t nNonce; // 4 nonce};","text":"比特币pow难度验证何为POWpow的全称为proof of work,即工作量证明。简单的解释为“做了多少工作”。抛开区块链的背景,pow就是对自己做了多少工作的一种说明:比如做了学习了50个小时的汽车驾驶。而他人很容易验证这个结果:你可能50个小时之后拿到了一本驾照。别人就知道你确实在学习驾驶上使用了50个小时。 Block Header在区块链的世界里,pow的数据可以体现在区块链的区块头中。当然一般来说,讲解POW的难度离不开挖矿问题。本文因为主要讨论方向的问题,不展开讲挖矿,主要从区块头入手。在阅读下面的内容之前,默认读者已经有了如下前置知识 区块链常识 比特币基本概念 挖矿基本概念 抛开前置知识之后,我们来看区块头的数据结构。 https://en.bitcoin.it/wiki/Protocol_documentation#Block_Headers 可以直接参考以上链接,当然可以可以直接查看比特币的源码,我们现在把数据列出来。 12345678struct header_structure { // BYTES NAME uint32_t nVersion; // 4 version uint8_t hashPrevBlock[32]; // 32 previous block header hash uint8_t hashMerkleRoot[32]; // 32 merkle root hash uint32_t nTime; // 4 time uint32_t nBits; // 4 target uint32_t nNonce; // 4 nonce}; 现在逐条解释block header中的字段。 nVersion 版本号,主要用来跟踪软件版本(bitcoin core)和协议号 现在固定为2 hashPrevBlock 前一个节点的hash值,我们知道区块链的链,大致指的是数据链式存储。我们可以简单粗暴的理解为它是指向前置节点的链表。 hashMerkleRoot 默克尔根,区块中所有交易组合起来生产成本的默克尔树的根。详情可参考数据结构“默克尔树” nTime 时间戳,标志块生成的时间 nBits 和难度有关,本文讨论的重点 nNonce 随机值,和难度有关,本文讨论的重点 基本概念现在介绍基本概念,方便理解: 挖矿是矿工之间互相竞争的结果,谁先算出有效区块,谁就可以得到块中的比特币奖励,它叫做coinbase,另外块中有交易,交易中蕴含的手续费也是矿工的奖励 有效区块的意思是:区块头的SHA256结果要小于等于一个目标值(target) merkleroot是矿工从内存池取出来的“交易”构建的。 大致上是先构建,再验证 对区块头做SHA256运算,如果结果小于等于一个目标值(target),则新块构建成功,广播到全网,随后开始下一次算力竞赛 否则,调整区块头部分字段的值(修改Nonce或者通过调整交易来修改Merkle Root),重新做计算 目标值target前一段提到了,区块头hash计算之后小于等于目标值才算有效区块。注意区块头哈希算法SHA256有256位,而bits只有32位。bit转换成target有特定公式: target = coefficient * 256^(exponent – 3) 随机抽取一个幸运区块来分析一下,我们就选高度为88888的区块。 12bits 454,373,987注意:这个bits是10进制,我们分析时候需要转16进制 对应为 0x1b153263 bit值是按照系数+指数的方式存储的,前两位为幂,后六位为系数。 1234561b为幂(exponent) 153263为系数(coefficient)注意这是16进制根据公式target = (0x153263)* 256^(1b - 3)target = 1389155 * 256^(27 - 3) = 8719867261221084516486306056196045840260667577454435863762042880 = 00 00 00 00 00 15 32 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 需要注意的有:计算的时候还是转10进制,计算完之后要转16进制,不够64位的要在高位补零。 实际上我们发现,实际的hash 000000000013a2a3a8f1aaec4690f55fdcb4067d812521a6d55239d8ea1a4dd3 比目标target确实是小的。 难度查看创世区块 我们发现 bits为0x1d00ffff (486604799)。我们把这个难度定为1 根据上面的公式,我们可以很简单的计算出target 0x00ffff * 256^26 0x00000000ffff0000000000000000000000000000000000000000000000000000 也就是说下一个区块头的sha256哈希值要小于这个值(前三十二位为0)。 target用16进制表示,每两个十六进制位表示一个字节bytes,前面有8个0,相当于4个字节,为4*8 = 32位(2进制位bit) 这里有个重要结论: SHA256运算的结果被认为是 一致的随机序列 。换个说法:SHA256的结果中的某一位的值,为0或为1的概率相同。所以做一次计算,满足上述条件(前32位的值均为0)的概率为1 / (2^32),也就是平均要做2^32次运算,才能找到这个值 关于一致的随机序列:可以看这个 在此之上,我们定义:“使区块头的SHA256结果小于某个目标值(target),平均要尝试的计算次数,这个次数为难度(difficulty)”。创世区块的难度为1! 比特币有这样一个公式 difficulty—current = target-genesis / target-current 随机选取一个幸运高度算一个 选择500000 12345678910111213bits: 402,691,653Difficulty: 1,873,105,475,221.61bit转16进制 18 009645target = (0x009654) * 256^(18-3) = 38484 * 256^(24-3) 创世块的target = (0x00ffff * 256 ^ 26) 两者相除 = 65535/38484 * 256^5 = 1.70291549735 * 2 ^ 40 = 1,873,105,475,221.61 = 1.87 * 10^12 可以看到计算出的难度和区块链浏览器展示的难度一致。大约为1.87T。 此外,还有一个计算公式 出块时间(单位:秒) ≈ difficulty_当前 * 2^32 / 全网算力 有兴趣的话可以去区块链浏览器上计算一下,在当前的难度下,出块时间是不是保持在10分钟左右。 难度的调整由上述计算得知,区块链难度取决于target值。target值和bits值相关。根据区块链的设计,要保证在10分钟左右出块。实际上,在难度不变的情况下,出块速度和全网算力相关,全网算力越大,则出块时间越短。为了保证出块时间的基本恒定,比特币在代码中内置了一个调整难度的算法。实际上这个算法很简单: 每产生2016个块,期望时间为2016*10min,大约两周。全网实际出2016个块的时间和期望时间做对比(times对比),小于期望就增加难度,大于期望值就减小难度。总体保持恒定。 根据比特币的源码,调整难度也是有限度的,如果调整的倍数超过4倍,按照4倍或者四分之一来调整。再次强调一遍,区块中的难度不是直接写入区块头的,是调整bits值得到的。有兴趣可以看这里。 注意:实际代码中2015个块就会调整,因为当初写代码写错了。。。 关于难度验证的闲话区块链pow难度验证如何保证数据的正确性 这里面有两个问题:交换数据的时候如何保证这个块是正常的块,不是伪造的? 答:区块链像链表一样,要指向前一个块。本区块中的的prvehash要和前一个区块的哈希一致。另外就是要计算pow难度,大致相当于之前的target计算过程,target要满足要求说明才是正常的块。 再进一步:如果难度满足要求,就是正常的块,有没有可能同时有两个以上有效块产生? 完全可能,因为coinbase不一样,merkle树不一样,nonce值也不一样,计算出来hash也不一样,是存在两个不一样的块都满足要求都 被接受的。但是,总有一个块会更快得到其他块,链接上。所以最长的链才是有效链。 安全广播的问题? 答:所谓安全广播,就是指如何应对别人窃取你的工作量,别人是否可以拿你打包的那些交易?答案是不可以,打包的时候,要从交易池获取对应的交易,其中第一个交易是自己的coinbase交易,如果你先成功,相当于矿工已经锁定了交易,锁定了merkletree。别的矿工只好开始下一轮竞争。换句话说:你打包了就是你的,别人只能放弃。这里有个常见的误解,因为交易是从内存池取过来的,交易不一样得到的merkler树不一样,会影响块hash计算,那我岂不是永远无法的的计算出合适的hash?实际在挖矿过程中,矿工会按照交易费高低排序,优先高交易费的交易打包进块。他为了快,选取什么交易(先算好),times是什么都是固定的(只要比上一个大就行),专心寻找nonce值即可。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"POW","slug":"POW","permalink":"https://imzy.vip/tags/POW/"}]},{"title":"拜占庭将军问题简述","slug":"拜占庭将军问题简述","date":"2021-05-11T22:05:53.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/27143/","link":"","permalink":"https://imzy.vip/posts/27143/","excerpt":"拜占庭将军问题简述拜占庭的简述本文将用于介绍著名的拜占庭将军问题。在介绍拜占庭将军问题之前,先简单说一下拜占庭。拜占庭帝国,在古代西欧也被称之为东罗马帝国。是一个位于欧亚交界处的封建君主制国家,其领土包括现在的欧洲南部,西亚和北非。公元四世纪左右,罗马帝国开始分列为罗马东部和罗马西部,这两个国家都被视为罗马正统,大概到公元十世纪,罗马西部陷落,随着神圣罗马帝国的建立,罗马东部失去了罗马这个单词的独占权,开始被称之为东罗马帝国。大概在16世纪之后,开始出现了拜占庭帝国的说法。值得一提的是,拜占庭帝国的首府君士坦丁堡被第四次十字军东征攻陷,从此一蹶不振。1453年5月29日,君士坦丁堡被强大的奥斯曼帝国攻陷,末代皇帝君士坦丁十一世战死。东罗马帝国就此终结。当然,此文不是介绍东罗马帝国历史的文章,在此仅简述拜占庭帝国的历史。 拜占庭将军问题是什么拜占庭将军问题和拜占庭的历史其实并无关联,拜占庭将军问题也不是历史上真实存在的问题,他是著名计算机大神兰伯特于1982年提出的。拜占庭问题描述的是如下的场景 假设有一座城堡,拜占庭帝国想攻陷这座城堡。所以派出了很多支军队,因为通讯条件很落后,军队和军队之间必须经过信使来传递作战命令。城堡十分坚固,可以抵御一两只军队的进攻。只有所有军队同时进攻,才可以攻陷城堡。为了保证作战命令的统一,提出一个办法,投票。超过半数投票决定作战命令。比如:决定明天早上进攻,如果有半数的军队同意这个作战计划。则在明日早晨开始一起进攻。反之,如果一大半人都不同意明天早上进攻,则明早就不进攻。 现在存在的问题是:军队中有叛徒。叛徒会随意调整作战命令 我们现在用图一来表示这种困境,在图示中,我们用黄色的小人代表正常的部队,粉色的小人代表判叛徒。A表示进攻Attack,R代表拒绝Reject。因为正直的的部队会忠实的执行命令,所以我们把A和R标记在他们身上。 如图所示,身上标记A的三位将军表示他们决定“明早发动进攻”,身上标记R的三位将军决定明早不应该发动进攻,现在叛徒至关重要。叛徒的这一票将决定明早是否决定进攻。这时候叛徒给三位决定进攻的将军传递的消息是明早发动进攻,给三位不进攻的将军发送的消息是明早不发动进攻。所以三位决定进攻的将军知道了,有4票发动进攻的,那么大多数人决定进攻那我明早就发动攻势。三位不进攻的将军则知道,有四票不发动进攻的,那我明早不进攻。所以拜占庭难度就产生了,这些部队有的进攻有的撤退,战斗就失败了。","text":"拜占庭将军问题简述拜占庭的简述本文将用于介绍著名的拜占庭将军问题。在介绍拜占庭将军问题之前,先简单说一下拜占庭。拜占庭帝国,在古代西欧也被称之为东罗马帝国。是一个位于欧亚交界处的封建君主制国家,其领土包括现在的欧洲南部,西亚和北非。公元四世纪左右,罗马帝国开始分列为罗马东部和罗马西部,这两个国家都被视为罗马正统,大概到公元十世纪,罗马西部陷落,随着神圣罗马帝国的建立,罗马东部失去了罗马这个单词的独占权,开始被称之为东罗马帝国。大概在16世纪之后,开始出现了拜占庭帝国的说法。值得一提的是,拜占庭帝国的首府君士坦丁堡被第四次十字军东征攻陷,从此一蹶不振。1453年5月29日,君士坦丁堡被强大的奥斯曼帝国攻陷,末代皇帝君士坦丁十一世战死。东罗马帝国就此终结。当然,此文不是介绍东罗马帝国历史的文章,在此仅简述拜占庭帝国的历史。 拜占庭将军问题是什么拜占庭将军问题和拜占庭的历史其实并无关联,拜占庭将军问题也不是历史上真实存在的问题,他是著名计算机大神兰伯特于1982年提出的。拜占庭问题描述的是如下的场景 假设有一座城堡,拜占庭帝国想攻陷这座城堡。所以派出了很多支军队,因为通讯条件很落后,军队和军队之间必须经过信使来传递作战命令。城堡十分坚固,可以抵御一两只军队的进攻。只有所有军队同时进攻,才可以攻陷城堡。为了保证作战命令的统一,提出一个办法,投票。超过半数投票决定作战命令。比如:决定明天早上进攻,如果有半数的军队同意这个作战计划。则在明日早晨开始一起进攻。反之,如果一大半人都不同意明天早上进攻,则明早就不进攻。 现在存在的问题是:军队中有叛徒。叛徒会随意调整作战命令 我们现在用图一来表示这种困境,在图示中,我们用黄色的小人代表正常的部队,粉色的小人代表判叛徒。A表示进攻Attack,R代表拒绝Reject。因为正直的的部队会忠实的执行命令,所以我们把A和R标记在他们身上。 如图所示,身上标记A的三位将军表示他们决定“明早发动进攻”,身上标记R的三位将军决定明早不应该发动进攻,现在叛徒至关重要。叛徒的这一票将决定明早是否决定进攻。这时候叛徒给三位决定进攻的将军传递的消息是明早发动进攻,给三位不进攻的将军发送的消息是明早不发动进攻。所以三位决定进攻的将军知道了,有4票发动进攻的,那么大多数人决定进攻那我明早就发动攻势。三位不进攻的将军则知道,有四票不发动进攻的,那我明早不进攻。所以拜占庭难度就产生了,这些部队有的进攻有的撤退,战斗就失败了。 以上的图示和背景就是拜占庭问题或者称之为拜占庭容错问题。 兰波特之问所以兰波特提出这个问题是为什么呢? 兰波特描述的是:世界上有很多个计算机,分布在世界上不同的位置。这些分布在不同地理位置的计算机,就像是驻扎在不同城市的将军们一样。但是计算机会出故障,也有可能会有黑客攻击,或者直接加入恶意节点。这些坏掉的计算就相当于叛徒。在这种状况下,我们如何保持一致性,就是指这些忠诚的计算机,他们的结果是一样的。另外我们关心的问题是,如何保持正确性。这里对正确性作出解释:如果大多数将军说要进攻,那我们就应该进攻,如果大多数将军说撤退,那我们就该撤退。尽管系统中存在故障和恶意节点,但是我们大多数节点都可以保持一致性和正确性,我们的系提还是正常的。这个问题发展至今,已经有四十余年的历史了。 为此也提出了分布式系统的原则 一致性 正确性 兰波特针对这个问题提出了自己的解法。 口头协议 书面协议 为了描述口头协议,我们提出了一个将军副官模型,其实哪个人是将军,哪个人是副官并不重要。所谓将军就是第一个发起命令的人:比如将军做出决定明早进攻,然后传递给其他人。其他人都叫做副官,副官可以执行将军的命令。为此我们需要两个假设 忠诚副官,所有忠诚的副官必须执行同一个命令。比如一起进攻,一起撤退。 忠诚将军,如果将军是忠诚的,所有忠诚的副官必须执行他的命令。 以上的假设的前提是忠诚,因为无论是司令还是副官,他们都有可能是叛徒,叛徒的行为不可预测。以上两个原则分别对应分布式协议的基本原则,一致性,准确性。 怎么做到这一点呢 兰波特提出,假如一共有n个将军,其中有m个叛徒。当n>3m时,这个问题是可解的。 比如我们有10个将军,有2个是恶意的10>3*2,这个问题是可解的。但是有三个人,其中有一个是叛徒,那这个问题是不可解的。因为没有满足n>3m 举个栗子: 假定有3个将军1个叛徒,n = 3, m = 1 如图所示,身上带有c标志的为commander,表示为发起命令的人。其他人副官,身上标记1,2。继续用粉色表示叛徒。现在将军给副官1,2发送进攻命令。忠诚副官1接到进攻命令之后并不会决定发起进攻,因为他也不知道将军是否是中忠诚的,所以他继续询问2,是否发动进攻。但是2是叛徒,他自己接受到c的进攻命令,他传递给1的命令是不进攻。这时1会收到两个消息,进攻和不进攻,1比1。所以他就没办法决定自己是进攻还是不进攻。 接下来,我们转换视角,考虑将军是叛徒的问题。将军分别给1下达了进攻指令,给2下达了撤退指令。此时1收到进攻指令后,继续询问2。2忠诚的传递了自己收到的命令不进攻。此时1收到的信息又是一票进攻一票撤退,无法决定。2面临的也是同样的问题。所以在n=3,m=1的情况下,这个问题是无解的。 下面我们来看有解的状况。 下一种状况下,n=4,m=1。即一共有四个将军,一个叛徒。 还是依照之前图例表示各种部队的性质。将军c给副官1,2,3,4发送进攻的通知。1接到进攻的通知之后再询问2,到底接了什么命令。因为2是忠诚的,所以如实的传递自己得到是进攻A。然后再询问副官3。副官3因为是叛徒,告诉1自己得到的是撤退。这时,1一共接到3个消息,两个进攻一个撤退。按照多数原则,他就可以确定第二天可以进行进攻。如果我们按照同样的方法去分析2,他得到结果是类似的。所以决策之后,所有忠诚的副官均进行一样的命令进攻,而且在司令官为忠诚的情况下,所有副官执行和司令官一样的命令进攻。刚好符合前面定下来的原则。 接下来我们看更加复杂的状况,将军是叛徒的情况。假设将军给1,2,3号分别发送了进攻,进攻和撤退三个命令。 我们站在1的角度,接受到司令的进攻命令,然后和2交换信息得到一个进攻,再和3交换信息。得到两个进攻和一个撤退,所以可以决定执行进攻。 从2的视角出发,2接到司令官的进攻信息,从1得到进攻信息,从3得到撤退信息。所以可以执行进攻。 从3的视角出发,3从司令官那里接到撤退信息,从1得到进攻,从2得到进攻。所以可以执行进攻。 总体来看,因为司令官不是忠诚的,所以不用顾虑司令官忠诚的条理。同时123都是忠诚的,他木门同时执行进攻。也符合假设。所以这种情况下,是可解的。 我们再次提升复杂度,探讨有两个叛徒的情况,即便m=2。根据公式,要向问题有解,我们必须要有七个人。n=7 情况较为复杂,就不在图上标注各位副官之间的信息交互情况,仅标注司令官给各位副官下达的命令。再描述这个情况情况之前,先强调一下这里分析需要的思路:递归。递归简单来说就是套娃,比如你照镜子的时候,再拿第二面精致对着镜子中的自己。或者你在直播的时候,用摄像头对着自己的屏幕。这里不是专门的算法分析介绍,不再强调递归。 回到图上,我们假设司令官是忠诚的,其中5和6号副官是叛徒。他给每个副官都下达了进攻命令。 下面我们来列一个表格表述现在的状况 拜占庭将军问题 m=2 V1=A V2 V3 V4 V5 V6 V2==A A A A a b V3== A A A c d V4== V5== g h i j k V6== 我们从1出发来看这个表格,看看一号副官是如何决策的。1毫无疑问接到了将军的进攻命令。但是他不可能接受到将军的命令就进攻,还是要交流,确定别人都接受到的是什么命令。 首先他要确定2接受的是什么命令。 第一重分析,他直接问V2,V2接受到了什么命令。2是忠诚的自然是说:2接受的是进攻。然后1开始问V3:V2接受的是什么命令。依次类推。因为5号和6号是叛徒,他说什么都可能,随意用无关字母小写的a,b来表表示。根据多数原则,1可以确定一件事,2号接受的确实是进攻命令。 接下来他开始确定v3到底接受的是什么命令。忠诚副官其实得到的信息分析都是类似的,我们不再重复。 我们直接开始决策V5和V6,即开始确定5号和6号得到的是什么命令。以5为例,因为2是叛徒,所以我们直接用不同的字母表示他给别人说自己的到的是什么。最终我们其实不清楚他所说的大多数命令是什么,所以用X表示,他可能是进攻也可能是撤退。 每一类的问题分析完之后,1不难得出结论,2,3,4号得到命令是进攻。至于5和6他们得到什么样的命令并不会影响最终决定。所以再分析完A的决策状况之后,他的决策就确定下来了:进攻。 有兴趣的话们可以自行填充上面的表格,也可以站在2,3,4,5,6的角度上再次进行分析。总而言之,这种状况下,拜占庭将军问题是有解的。 最终结论,1,2,3,4会选择进攻,而且忠诚的将军下发的进攻没命令也会被忠诚的副官执行。 有兴趣的话,也可以推演一下将军和6号是叛徒的情况。 通过以上的分析不难看出,这个算法的复杂度相当之高。而且波兰特提出这个问题的时候是不考虑网络延迟的,所以这个方法在计算机中并不会被使用。后来又有人提出了更加高效的PBFT算法。有兴趣的话可以阅读相关的论文。 结尾拜占庭容错问题和区块链息息相关。区块链的问题也是要解决一致性问题。简单来说,以比特币为首的区块链提出了POW算例证明。需要节点都参与解决数学题,如果率先解决数学难题,则有记账的权利。无疑增加了恶意节点的从成本,你必须别其他节点做的更快才有作恶的权利。后来还有也陆续提出了POS算法,DPOS算法等。也是为了解决本文开始提到的一致性和正确性问题。希望本文可以帮大家建立对于拜占庭容错问题的基本概念。 本文完!","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"}]},{"title":"在M1上编译substrate","slug":"在M1上编译substrate","date":"2021-03-11T10:01:02.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/31576/","link":"","permalink":"https://imzy.vip/posts/31576/","excerpt":"之前已经在x86的mac电脑上编译过substrate,按照官方指南上的操作就可以正常编译。但是在新款m1电脑上并没有编译通过,现在重新尝试在m1上编译substrate。 主要的准备过程参考如下文章 https://zhuanlan.zhihu.com/p/337224781 不过参考文章写于2020年12月16日,到现在(2021年3月10日)有部分状况已经发生了变化。针对和文章中不一样的状况稍作说明。 RUSTrust环境现在可以直接支持m1。所以使用rustup脚本可以直接安装rust,不需要额外设置。安装完成之后使用 rustup show 查看toolchain。则会发现是以aarch64开头的,原来的x86下面的tool-chain是 stable-x86_64-apple-darwin (default) 注意差别。","text":"之前已经在x86的mac电脑上编译过substrate,按照官方指南上的操作就可以正常编译。但是在新款m1电脑上并没有编译通过,现在重新尝试在m1上编译substrate。 主要的准备过程参考如下文章 https://zhuanlan.zhihu.com/p/337224781 不过参考文章写于2020年12月16日,到现在(2021年3月10日)有部分状况已经发生了变化。针对和文章中不一样的状况稍作说明。 RUSTrust环境现在可以直接支持m1。所以使用rustup脚本可以直接安装rust,不需要额外设置。安装完成之后使用 rustup show 查看toolchain。则会发现是以aarch64开头的,原来的x86下面的tool-chain是 stable-x86_64-apple-darwin (default) 注意差别。 brewmac上的包管理离不开brew,所以一定需要安装brew。brew现在也已经官方支持m1,不再需要像参考文章中的特殊设置,直接使用官方脚本安装即可。brew在mac下有两个目录,我们暂时只关心在m1下的原生文件目录。可以cd到一下目录查看 1/opt/homebrew 确保文件存在,如果你使用的是zsh,则在.zshrc中设置如下,如果你使用bash则在~/.bash_profile添加如下 1export PATH="/opt/homebrew/bin/:$PATH" substrate 配套环境安装好brew之后,你需要下面四个依赖 1234brew install -s cmakebrew install -s gccbrew install protobufbrew install -s llvm 依次安装即可。注意设置llvm的环境变量中。比如我使用zsh. 就在~/.zshrc中写入 1export PATH="/opt/homebrew/opt/llvm/bin:$PATH" 以上安装注意看brew的提示。brew也会提示你设置以上环境变量。 每次写入环境变量记得source. 编译substrate本文以官方教程为例 https://substrate.dev/docs/en/tutorials/create-your-first-substrate-chain/setup 我们使用2.0.1版本的substrate.截止目前(2020.3.10)substrate已经有3.0版本,但是本文以教程为准,使用2.01版本 在合适的目录克隆 1git clone -b v2.0.1 --depth 1 https://github.com/substrate-developer-hub/substrate-node-template 进入到node-temple目录后,需要升级如下两个依赖库 12cargo update -p fs-swapcargo update -p ring substrate还需要依赖rustrocksdb 1234$ cd ${anywhere}$ git clone https://github.com/hdevalence/rust-rocksdb.git$ cd rust-rocksdb$ git submodule update --init --recursive 我和参考文章一样把rust-rocksdb放在了如下位置 /opt/homebrew/opt/ 然后去修改~/.cargo目录下的config,注意默认情况下config文件是不存在的,首次设置之前你需要自己创建该文件。 然后在config文件中写入如下 1paths = ["/opt/homebrew/opt/rust-rocksdb/"] 之所以修改是因为原本依赖中的三个问题。 fs-swap原本的依赖在m1下无法编译,新版本解决了这个问题,所以需要使用cargo update升级 ring同理 rust-rocksdb也无法在m1下编译,所以感谢hdevalence,自己fork出来修改了问题。我们依赖这个fork出来的rocksdb 说明:在.cargp/config中使用这种path依赖并非最优选择,在项目本身中使用patch是更好的做法,稍后编译中会出现相应的警告提示这一点。关于如何使用patch请参考《cargo book》,本文不做讨论。此外提示,对于substrate这种以workspace组织起来的项目,patch信息写在根目录下面的Cargo.toml中。 接下来编译还会出现一个问题,报错的形式如下 1234567error[E0609]: no field `__rip` on type `__darwin_arm_thread_state64` --> crates/runtime/src/traphandlers.rs:169:44 |169 | (*cx.uc_mcontext).__ss.__rip as *const u8 | ^^^^^ unknown field | = note: available fields are: `__x`, `__fp`, `__lr`, `__sp`, `__pc` ... and 2 others 关于这个错,我们可以参考 https://github.com/bytecodealliance/wasmtime/issues/2575 讨论中已经提到了这个问题的解决办法,所以我们直接把__ rip换成__ pc即可。提供一个最快捷的修改办法 直接进入到错误提示的源码处,直接修改依赖库的源码,把_ _ rip改成 _ _ pc。 注意:这么改是为了快捷,绝非良策。因为rust库的构建依赖cargo管理,我们直接去修改了库的源码,如果使用cargo update,或者其他触发了Cargo.lock变动的操作,依然可能编译不过。 好的办法还是fork下来使用patch依赖。 所有准备工作做完之后就可以 1cargo build --release 大概七分钟左右就可以编译完成。比原本的x86 mac快很多。 本文的编译的环境汇总 Rustc 使用1.50.0版本,可以使用rustup show查看 Substrat-node-template 使用v2.0.1版本 硬件环境为 m1 mac 操作系统 macOS Big Sur 11.2.3 一些建议经常看到讨论substrate编译不过问题的。经常面对这种问题可以做一个详细的文档来记录这些,文档中可以包含以下内容 rust tool-chain的版本 可以编译操作系统信息 比如linux某版本 windows某版本,mac有m1版本和x86版本也需要额外说明 需要额外安装的依赖,比如这里提到的llvm protobuf 基于某个substrate版本开发的 比如本文基于v2.0.1 基于当前版本substrate需要做的某些调整,比如本文中提到的ring fs-swap升级 wasmtime的修改等。 如果版本升级了,需要配套更新以上这一套信息(这里的版本升级包含rust升级和substrate升级) 如果有必要可在项目下专门建立一个patch文件,来管理需要patch的包。 相信做到以上的7点,就不用反复的去解决编译不通过的问题。 本文完。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"substrate","slug":"substrate","permalink":"https://imzy.vip/tags/substrate/"}]},{"title":"sqlite自增小知识","slug":"sqlite自增小知识","date":"2021-01-20T10:06:54.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/48571/","link":"","permalink":"https://imzy.vip/posts/48571/","excerpt":"Sqlite自增字段起因:在使用数据库存储从区块链网络上取来的block_header时,block_header本身并不带自身的高度信息。不过取来的数据是经过筛选的,按照数据库存储的顺序就可以代表blockheader的高度。所以在数据库中增加一个id primiry key autoincrement 的自增主键。每次从数据库查询时获取id值来代表区块的高度。但后来重构,id被TEXT类型的逐渐替代,所以这个功能无法正常实现。所以产生以下疑问 在已经有TEXT类型的主键后,sqlite可以拥有别的自增字段吗?不可以!在sqlite的文档FAQ中第一个问题(文末给出参考链接)就是关于如何设置自增字段的。在sqlite中自增约束AUTOINCREMENT只可以跟在PRIMARY KEY后面。把AUTOINCREMENT放在主键以外的地方是不可以的。或者再明确一点,要想在sqlite中拥有一个自增字段必须这样写 1id INTEGER PRIMARY KEY AUTOINCREMENT, 要求id的类型必须是INTEGER。每次插入数据库的时候,不要插入id的数据,数据库会自动为我们的主键id实现自增。之前提到的情况:id已经是TEXT PRIMARY KEY的状态下,无法再拥有另一个自增字段了。 题外:在sqlite以外的数据库中是可以的。以mysql为例,mysql的AUTOINCREMENT是可以加在主键之外的地方的。一个表中,只允许有一个自增字段,而且在mysql中需要给主键以外的字段实现自增,必须给该字段加上unique约束。 注意点: 在设定id INTEGER PRIMARY KEY,不加AUTOINCREMENT,只要不指定插入id的数据,id字段也可以实现自增。 在已经有TEXT类型的主键后,一定要有一个自增的字段来记录当前所在的行数怎么办?","text":"Sqlite自增字段起因:在使用数据库存储从区块链网络上取来的block_header时,block_header本身并不带自身的高度信息。不过取来的数据是经过筛选的,按照数据库存储的顺序就可以代表blockheader的高度。所以在数据库中增加一个id primiry key autoincrement 的自增主键。每次从数据库查询时获取id值来代表区块的高度。但后来重构,id被TEXT类型的逐渐替代,所以这个功能无法正常实现。所以产生以下疑问 在已经有TEXT类型的主键后,sqlite可以拥有别的自增字段吗?不可以!在sqlite的文档FAQ中第一个问题(文末给出参考链接)就是关于如何设置自增字段的。在sqlite中自增约束AUTOINCREMENT只可以跟在PRIMARY KEY后面。把AUTOINCREMENT放在主键以外的地方是不可以的。或者再明确一点,要想在sqlite中拥有一个自增字段必须这样写 1id INTEGER PRIMARY KEY AUTOINCREMENT, 要求id的类型必须是INTEGER。每次插入数据库的时候,不要插入id的数据,数据库会自动为我们的主键id实现自增。之前提到的情况:id已经是TEXT PRIMARY KEY的状态下,无法再拥有另一个自增字段了。 题外:在sqlite以外的数据库中是可以的。以mysql为例,mysql的AUTOINCREMENT是可以加在主键之外的地方的。一个表中,只允许有一个自增字段,而且在mysql中需要给主键以外的字段实现自增,必须给该字段加上unique约束。 注意点: 在设定id INTEGER PRIMARY KEY,不加AUTOINCREMENT,只要不指定插入id的数据,id字段也可以实现自增。 在已经有TEXT类型的主键后,一定要有一个自增的字段来记录当前所在的行数怎么办?sqlite数据库中自带一个字段叫做 rowid,rowid就是一个自增字段。我们查询的时候,直接查询rowid就可以知道当前行数。(但是sqlite有without rowid类型表,这种表不带rowid字段)。一旦我们在数据库中设定 id INTEGER PRIMARY KEY,id就相当于rowid的别名。在sqlite3中,rowid的大小为64-bit signed integer,大小范围为i64。rowid最大值为9223372036854775807。 rowid的行为rowid由数据库自行维护,只要用户不去主动干涉。rowid每行加一。当rowid超过以上的范围时刻,会在允许范围内随机找还没占用到的数字填充。当我们删除行之后,rowid所代表的数字是可以被复用的。如果所有数字确实用完之后数据库会抛出SQLITE_FULL error。 rowid和序列在sqlite数据库中,如果启用了AUTOINCREMENT,数据库会有一个内建的表叫做sqlite_sequence来记录rowid的变化。注意:这个序列表是可以正常操作的,比如插入数据,更新数据。修改的前提是用户要清楚自己操作对数据库有何影响。当数据库插入数据时,序列表就会关联的发生变化。因为序列表的存在,加了AUTOINCREMENT的数据库要稍微慢一点。且序列表由数据库创建和删除,不允许用户自行创建和删除。 其他注意事项: 1ROWID, _ROWID_, OID都可以用来表示rowid 参考资料: https://sqlite.org/faq.html#q1 https://sqlite.org/autoinc.html","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"数据库","slug":"数据库","permalink":"https://imzy.vip/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"}]},{"title":"多线程原语ConVar","slug":"多线程原语CondVar","date":"2020-12-24T16:33:46.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/63616/","link":"","permalink":"https://imzy.vip/posts/63616/","excerpt":"多线程原语CondVar多线程下的原语,除了我们常用的锁,还有另外一类用于同步的原语叫做“屏障”,“条件变量”(在rust或者cpp中)。在其他语言中也有类似的概念,叫做栅栏,闭锁,屏障,信号量等。他们具有相同的意义。 在介绍条件变量之前,先介绍屏障(Barrier)。屏障相当于一堵带门的墙,使用wait方法,在某个点阻塞全部进入临界区的线程。条件变量(Condition Variable)和屏障的语义类似,但它不是阻塞全部线程,而是在满足某些特定条件之前阻塞某一个得到互斥锁的线程。 单纯讲条件变量的意义并不直观。换种描述 条件变量可以在我们达到某种条件之前阻塞线程,我们利用此特性可以对线程进行同步。或者说做到按照某种条件,在多个线程中达到按照特定顺序执行的目的。 为此我们设计如下下面流程。为此流程写一段代码,来体会条件变量的作用 我们启动三个线程,t1,t2,t3。分别执行任务T1,T2,T3。现在要求:T2必须等待T1和T3完成之后再执行 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162use parking_lot::{Mutex, Condvar};use std::sync::Arc;use std::thread;use std::thread::sleep;use std::time::Duration;pub fn main() { let pair = Arc::new((Mutex::new(0), Condvar::new())); let pair2 = pair.clone(); let pair3 = pair.clone(); let t1 = thread::Builder::new() .name("T1".to_string()) .spawn(move || { sleep(Duration::from_secs(4)); println!("I'm working in T1, step 1"); let &(ref lock, ref cvar) = &*pair2; let mut started = lock.lock(); *started += 2; cvar.notify_one(); } ) .unwrap(); let t2 = thread::Builder::new() .name("T2".to_string()) .spawn(move || { println!("I'm working in T2, start"); let &(ref lock, ref cvar) = &*pair; let mut notify = lock.lock(); while *notify < 5 { cvar.wait(&mut notify); } println!("I'm working in T2, final"); } ) .unwrap(); let t3 = thread::Builder::new() .name("T3".to_string()) .spawn(move || { sleep(Duration::from_secs(3)); println!("I'm working in T3, step 2"); let &(ref lock, ref cvar) = &*pair3; let mut started = lock.lock(); *started += 3; cvar.notify_one(); } ) .unwrap(); t1.join().unwrap(); t2.join().unwrap(); t3.join().unwrap();} 以上代码可以在 这个链接 下在playground运行。 上面的代码需要注意的点如下","text":"多线程原语CondVar多线程下的原语,除了我们常用的锁,还有另外一类用于同步的原语叫做“屏障”,“条件变量”(在rust或者cpp中)。在其他语言中也有类似的概念,叫做栅栏,闭锁,屏障,信号量等。他们具有相同的意义。 在介绍条件变量之前,先介绍屏障(Barrier)。屏障相当于一堵带门的墙,使用wait方法,在某个点阻塞全部进入临界区的线程。条件变量(Condition Variable)和屏障的语义类似,但它不是阻塞全部线程,而是在满足某些特定条件之前阻塞某一个得到互斥锁的线程。 单纯讲条件变量的意义并不直观。换种描述 条件变量可以在我们达到某种条件之前阻塞线程,我们利用此特性可以对线程进行同步。或者说做到按照某种条件,在多个线程中达到按照特定顺序执行的目的。 为此我们设计如下下面流程。为此流程写一段代码,来体会条件变量的作用 我们启动三个线程,t1,t2,t3。分别执行任务T1,T2,T3。现在要求:T2必须等待T1和T3完成之后再执行 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162use parking_lot::{Mutex, Condvar};use std::sync::Arc;use std::thread;use std::thread::sleep;use std::time::Duration;pub fn main() { let pair = Arc::new((Mutex::new(0), Condvar::new())); let pair2 = pair.clone(); let pair3 = pair.clone(); let t1 = thread::Builder::new() .name("T1".to_string()) .spawn(move || { sleep(Duration::from_secs(4)); println!("I'm working in T1, step 1"); let &(ref lock, ref cvar) = &*pair2; let mut started = lock.lock(); *started += 2; cvar.notify_one(); } ) .unwrap(); let t2 = thread::Builder::new() .name("T2".to_string()) .spawn(move || { println!("I'm working in T2, start"); let &(ref lock, ref cvar) = &*pair; let mut notify = lock.lock(); while *notify < 5 { cvar.wait(&mut notify); } println!("I'm working in T2, final"); } ) .unwrap(); let t3 = thread::Builder::new() .name("T3".to_string()) .spawn(move || { sleep(Duration::from_secs(3)); println!("I'm working in T3, step 2"); let &(ref lock, ref cvar) = &*pair3; let mut started = lock.lock(); *started += 3; cvar.notify_one(); } ) .unwrap(); t1.join().unwrap(); t2.join().unwrap(); t3.join().unwrap();} 以上代码可以在 这个链接 下在playground运行。 上面的代码需要注意的点如下 CondVar需要和锁一起使用,在运行中每个条件变量每次只可以和一个互斥体一起使用。 这里使用了parking_lot中的CondVar和Mutex,使用标准库中的条件变量和锁也是一样的效果。 设计中,在锁中持有了一个数字类型。当锁中的数字(也就是我们的“变量”)小于5,我们使用wait阻塞住t2。我们在t1完成时,把数字加二,在t3完成后,我们把数字加三。 注意,每次更改变量后要使用通知。 一般情况下,我们可以设计更复杂的变量和阻塞条件来达到更复杂的同步效果 特别注意的是 notify_one()只会通知线程一次,也就是说,如果我们有多个线程被阻塞住,notify_one会被一个阻塞地方消耗。不会传播到另一个阻塞的临界区中 notify_all()会通知所有阻塞区。 所以使用的时候需要特别注意两种通知不同的使用场景,避免造成阻塞。 在CondVar中还有对应的wait_for。可以设置TimeOut,避免造成永久的阻塞。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/tags/rust/"},{"name":"多线程","slug":"多线程","permalink":"https://imzy.vip/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}]},{"title":"Jni符号对照","slug":"Jni符号对照","date":"2020-07-10T10:16:48.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/55704/","link":"","permalink":"https://imzy.vip/posts/55704/","excerpt":"Jni符号表本文是之前博客文章 《使用rust写安卓库》 的延续。之前的文章主题是如何利用rust-jni库提供便于java使用的rust jni代码。本文在之前的基础上继续提供后续关于jni符号,或者type的说明。","text":"Jni符号表本文是之前博客文章 《使用rust写安卓库》 的延续。之前的文章主题是如何利用rust-jni库提供便于java使用的rust jni代码。本文在之前的基础上继续提供后续关于jni符号,或者type的说明。 起因写rust端代码的时候,如果我们想返回一个java端的对象我们的rust代码大概会按照以下写法(仅为示例,不可运行): 12345678910111213141516#[no_mangle]#[allow(non_snake_case)]pub extern "system" fn Java_JniApi_hello(env: JNIEnv, _: JClass, str: JString) -> jobject { let str = env.get_string(str).unwrap(); let message_class = env.find_class("JniApi$StatusMessage").expect("can't find class JniApi$StatusCode"); let message_obj = env.alloc_object(message_class).expect("create message instance error"); env.set_field(message_obj, "code", "I", JValue::Int(200)).expect("set code error"); env.set_field(message_obj, "message", "Ljava/lang/String;", JValue::Object(JObject::from(env.new_string("Rust".to_string()).unwrap()))).expect("set error msg value is error!"); message_obj.into_inner()}// 第一二行为rust Attribute,告知编译器相应行为的,和本文关联不大,不必关心// 函数名的命名在文章开始提供的那一篇文章中已经说的很清楚 代码的后两行注释说明了一些和本文无关的情况。以上的代码主要是生成java中的StatusMessage对象,这个对象中有两个field,message 和 code。在java端的对应的类型分别为int,String。以上代码就是填充StatusMessage返回给message端。根据jni库的规则(https://docs.rs/jni/0.16.0/jni/),我们需要以下三步 定位StatusMessage的class(熟悉java的人应该知道$之后代表内部类)。 alloc_object 分配内存 填充数据 以上三点是使用jni库的步骤。我们现在关心的点是 set_field中的参数代表什么? 根据jni库的文档,set_field有四个参数,第一个参数为class对象,第二个参数为具体filed的名字,第三个为type。第四个为需要具体填充的值。 本文的重点是关心 type到底是什么本文要解决的问题:type是什么,这里的type该怎么填充?type的填充主要看java端的规定。我们不从这一头看,我们回头去看java端。下面展示一个使用javac -h命令之后我们得到的java头文件。一般来说,我们不需要java头文件,但是我们要写jni,头文件是必须的。 123456789101112131415161718class HelloWorld { // This declares that the static `hello` method will be provided // a native library. private static native String hello(String input); static { // This actually loads the shared object that we'll be creating. // The actual location of the .so or .dll may differ based on your // platform. System.loadLibrary("mylib"); } // The rest is just regular ol' Java! public static void main(String[] args) { String output = HelloWorld.hello("josh"); System.out.println(output); }} .h文件 12345678910111213141516171819202122/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class HelloWorld */#ifndef _Included_HelloWorld#define _Included_HelloWorld#ifdef __cplusplusextern "C" {#endif/* * Class: HelloWorld * Method: hello * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_HelloWorld_hello (JNIEnv *, jclass, jstring);#ifdef __cplusplus}#endif#endif 生成的h文件之中,我们发现有一段注释,只解释Signature。 Signature为函数签名. 签名的格式为 (参数类型)返回值类型 所以参数为String,返回值为String。得到的类型就是示例中的签名样式。 下面给出type的参考表,或者使用专有名词,叫做Type Signatures Type Signatures Java Type Z boolean B byte C har S short I int J long F float D double Lfully-qualified-class fully-qualified-class [ type type[] ( arg-types ) ret-type method type 解释其中几点 L后面跟的是具体类型的路径 比如String 类型在signatures中就表现为 Ljava/lang/String [数组类型 最后一行表现的是signatuer的格式 按照以上规则举个例子 123456函数:long f (int n, String s, int[] arr); 对应签名(ILjava/lang/String;[I)J 注意 [ 只有单边。参数之间不间隔,只有具体类型才用;收尾 所以再对应到一开始提到的问题,type该怎么填写 12code 对应 StatusMessage 中的 int 类型。所以写 "I"message 对应 StatusMessage 中的 String 类型。所以写作 "Ljava/lang/String;" 再给出一个oracle的文章,它更好,更全面。无论是写rust还是写c/cpp的接口都参考它。 https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html#wp9502 全文完。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"jni","slug":"jni","permalink":"https://imzy.vip/tags/jni/"},{"name":"符号对照","slug":"符号对照","permalink":"https://imzy.vip/tags/%E7%AC%A6%E5%8F%B7%E5%AF%B9%E7%85%A7/"}]},{"title":"比特币是不是货币?","slug":"比特币是不是货币?","date":"2020-05-18T23:30:22.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/182/","link":"","permalink":"https://imzy.vip/posts/182/","excerpt":"比特币是不是货币今天晚上和人聊天的时候,提到的了这个话题。我开始从事比特币相关开发也有一段时间了,经常有人会提到这个话题。这个话题比”比特币是什么?”更加具体,我也被问到过多次。本着不重复劳动的初心,特意写这篇小文章来说说我的理解。","text":"比特币是不是货币今天晚上和人聊天的时候,提到的了这个话题。我开始从事比特币相关开发也有一段时间了,经常有人会提到这个话题。这个话题比”比特币是什么?”更加具体,我也被问到过多次。本着不重复劳动的初心,特意写这篇小文章来说说我的理解。 回答是否定的,比特币并非货币。 严格的来讲,比特币是一种数字通证。或者从技术角度来讲我们把这类东西都统称为Token。 按照货币学基本原理,货币是可以超发的。因为货币的一个重要基本功能是价值的尺度。我们讲一部手机价值3000元。这里的三千元就等于价值的尺度。它是用来衡量社会财富的标尺。 何为超发呢,超发就是简单来讲可以得到更多。比如政府可以根据经济情况印更多的货币。换句话来说,货币是可以变多的。但是比特币是不可以变多的,它的总量是固定的。 延伸的话题就是,为什么货币必须要超发?因为我们社会财富的增长是必然的趋势。随着生产力会进步,那么必然会有产出更多财富。我们就需要更多的货币来衡量价值。假入我们一共只有一千人民币,我们生产了一部手机,手机定价为一千元。那我们就用一千来衡量手机的价值。但是如果我们生产出两部手机,但是我们还是只有一千元,会怎么样呢?那就是这一千元可以买两部手机。创造了更多财富,但是手机的单价反而降低了。这就是对生产积极性的破坏,因为不多生产第二部手机,手机的价值更高。成了一种”奖懒罚勤”的奇怪局面。从反面来讲,钱变的更值钱了。这种情况下没有人会去花这一千元,因为傻子都知道:我把这一千元拿在手里,将来可以买越来越多的东西。 现在比特币就面临这种局面。比特币因为固定产出(比特币额定产量两千一百万枚),和货币的基本属性是相悖的。仅仅从这一点可以得出的结论:”比特币并非货币“。 当然,有很多角度去回答这个问题,我的观点也并非完美。但上面的话已经足够回答本文的问题。对于技术人员,我们可能更关心的是比特币的技术,比特币带来的变革,而不是关心它是不是货币。如果是非技术人员,可以理解为它是一种金融产品,和股票基金类似。 ps:本文由Github Actions自动构建。这是本博客首次依赖CI/CD自动构建,同时也首次使用sh进行自动上传。特别指出,作为纪念。","categories":[{"name":"科普","slug":"科普","permalink":"https://imzy.vip/categories/%E7%A7%91%E6%99%AE/"}],"tags":[{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"}]},{"title":"使用状态码加消息传递库状态","slug":"使用状态码加消息传递库状态","date":"2020-05-09T20:09:08.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/19624/","link":"","permalink":"https://imzy.vip/posts/19624/","excerpt":"这篇文章是上一篇使用rust写安卓库的续篇,本身按照上一篇文章的介绍已经完全基本讲清楚了使用rust给安卓写库的基本方面,这篇文章继续在之前的状态上补充,力求文章完备。","text":"这篇文章是上一篇使用rust写安卓库的续篇,本身按照上一篇文章的介绍已经完全基本讲清楚了使用rust给安卓写库的基本方面,这篇文章继续在之前的状态上补充,力求文章完备。 本文要解决的场景如下 当我们外部调用rust库时难免出现库本身状态出现问题,比如内部错误,比如参数错误。假如我们在只使用同一种语言,这种问题很好处理,善用语言错误处理机制即可。然而我们跨语言就出现了问题,如在之前文章设定的场景:java端的错误是Exception,rust端的错误是Result和Option。 所以我们采用状态码来传递错误信息。着重于传递错误信息。我们把想传递的消息给调用端即可。毕竟作为库的使用者,并不知道库内部的具体信息,只需要知道我作为传递信息者有什么问题就好了。库的问题由库来解决。 在设定以上场景之后,我们的问题就很容易解决。除去错误处理这个杂质,进一步简化问题为如下 调用端传递信息进库,库返回状态(对象)给的调用者。 唯一的问题就是跨语言。(这个属于具体使用rust-jni库的细节,如果要写一个跨语言的库,大体是这种处理方案) 然后我们来写代码(设定场景,java调用rust库。) 首先在java端定义错误本身 1234567891011121314151617181920 class JniApi { public class StatusMessage { public int code; public String message; } private static native StatusMessage send_message(String mnemonic_str); static { System.loadLibrary("murmel"); } // The rest is just regular ol' Java! public static void main(String[] args) { StatusMessage message1 = JniApi.sned_message("Yes"); System.out.println("------------------------------"); System.out.println(message1.message); }} 注意,以上的代码并不符合java规范。只是可用。如果规范,请给StatusMessage实现toString()方法。本文为了简化,没有写。 StatusMessage定义了消息本身,它很简单,一个状态码和一个状态消息。定义静态方法,返回StatusMessage本身。然后在main中调用静态方法。最后打印返回的StatusMessage对象。 接下来在rust端实现send_message方法本身 12345678910111213#[no_mangle]pub extern "system" fn Java_JniApi_send_message(env: JNIEnv, _: JClass, str: JString) -> jobject { let str = env.get_string(mnemonic_str).unwrap(); let message_class = env.find_class("JniApi$StatusMessage").expect("can't find class JniApi$StatusCode"); let message_obj = env.alloc_object(message_class).expect("create message instance error"); env.set_field(message_obj, "code", "I", JValue::Int(200)).expect("set code error"); env.set_field(message_obj, "message", "Ljava/lang/String;", JValue::Object(JObject::from(env.new_string("Rust".to_string()).unwrap()))).expect("set error msg value is error!"); message_obj.into_inner()} 对上面代码做简单说明。 首先,rust方法的命名来自于对java文件得到javac -h 操作。这个上一篇文章提到过。JNIEnv和JClass这两个是固定的参数,第三个参数是JString对应,静态方法中传入的字符串型参数。 代码得到正文是获取输入参数,他的使用方法是固定的,使用env参数获取 接下来我们填充需要返回的StatusMessage,所有的java class在rust端都被jni-rs抽象为jobject。 第一步,我们寻找class。我们使用env.fin_class()方法完成这一点。其中的参数代表类的名字。如果熟悉java,我们知道JniApi$StatusMessage,这种写法代表内部类。在javac -h生成的文件中我们也可以发现这一点。 第二步,分配内存。我们使用env.alloc_object()来实现 第三步,填充field。因为在java,我们把class持有的数据称为filed。具体参数请查看jni-rs的文档。单独提出来两点,对于int,我们理解为JValue::Int,对于的对象类型(java端的String)我们使用了JValue::Object。他们属于jni-rs使用的细节。 最后返回jobject对象即可 最后在总结成四个步骤 定位class 分配内存 填充field 返回jobject 以上已经完全说清楚如何返回给使用端一个java可以理解的”对象“。更复杂的情况也是以上四步大象装进冰箱法。当然其实这些都属于jni-rs的使用细节,我只是单独把它拿出来讲。 希望通过这两篇文章把如何使用rust给安卓写动态库的问题讲清楚。前一篇种配置和基本思路,这篇讲具体细节。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/tags/rust/"},{"name":"安卓","slug":"安卓","permalink":"https://imzy.vip/tags/%E5%AE%89%E5%8D%93/"}]},{"title":"使用rust写安卓库","slug":"使用rust写安卓库","date":"2020-03-31T16:33:36.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/27183/","link":"","permalink":"https://imzy.vip/posts/27183/","excerpt":"使用rust写安卓库rust写安卓库也是rust的一个重要应用方向,之前用来写安卓的库的语言大多数都是c/c++。本文不讨论两种(或者叫两类)语言的优劣,只说明如何搭建一个rust-android相互交互的环境。","text":"使用rust写安卓库rust写安卓库也是rust的一个重要应用方向,之前用来写安卓的库的语言大多数都是c/c++。本文不讨论两种(或者叫两类)语言的优劣,只说明如何搭建一个rust-android相互交互的环境。 Android使用Java语言,java与c/c++交互使用jni技术。Android与rust交互也使用jni。在这里我们使用jni-rs库。在使用rust写安卓库之前我们先简化一下问题。先用jni-rs编写一个可以被java调用的库。这个过程可以参考jni-rs的代码说明。但我自己使用的过程中稍微对文档进行了说明。 使用jni-rs库既然rust作为库,那java端就是使用者。所以一切使用以java端出发。先编写一个HelloWorld.java文件如下 1234567891011121314class HelloWorld { private static native String hello(String input); static { System.loadLibrary("mylib"); } // The rest is just regular ol' Java! public static void main(String[] args) { String output = HelloWorld.hello("TigerInYourDream"); System.out.println(output); }} 代码的第一行是将来rust库需要提供的方法,这里相当于提供一个接口,定义了函数名,参数和返回值。静态代码块是加载库用的,需要在实际调用之前加载。动态库的名称为 ”mylib“。现在是没有的。之后就是常规的调用。这里的调用是静态调用。 现在这段java代码无法运行,因为我们并没有”mylib“这个库。hello methon也需要在rust中实现。我们暂时不着急创建rust lib。我们在目录下使用javac -h命令 1javac -h . HelloWorld.java 注意要使用 .java 后缀,以免无法识别。 接下来会在目录下生成一个 .h 文件。这个文件如下 123456789101112131415161718192021/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class HelloWorld */#ifndef _Included_HelloWorld#define _Included_HelloWorld#ifdef __cplusplusextern "C" {#endif/* * Class: HelloWorld * Method: hello * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_HelloWorld_hello (JNIEnv *, jclass, jstring);#ifdef __cplusplus}#endif#endif 不要修改这个文件。我们看其中最重要的部分,我们定义rust方法就要按照 .h 文件中的要求来。方法名必须为 JNICALL Java_HelloWorld_hello。知道这一点后开始写rust lib。 直接创建cargo项目 mylib。 1cargo new mylib --lib 在项目的cargo.toml添加如下 12345[dependencies]jni = "0.16.0"[lib]crate_type = ["cdylib"] 这样配置之后如果你运行cargo build。在linux下会得到libmylib.so。在mac下会得到libmylib.dylib。 编写lib.rs 1234567891011121314151617181920212223242526272829use jni::JNIEnv;use jni::objects::{JClass, JString};use jni::sys::jstring;// This keeps Rust from "mangling" the name and making it unique for this// crate.#[no_mangle]pub extern "system" fn Java_HelloWorld_hello(env: JNIEnv,// This is the class that owns our static method. It's not going to be used,// but still must be present to match the expected signature of a static// native method. class: JClass, input: JString) -> jstring { // First, we have to get the string out of Java. Check out the `strings` // module for more info on how this works. let input: String = env.get_string(input).expect("Couldn't get java string!").into(); // Then we have to create a new Java string to return. Again, more info // in the `strings` module. let output = env.new_string(format!("Hello, {}!", input)) .expect("Couldn't create java string!"); // Finally, extract the raw pointer to return. output.into_inner()} Jni-rs库是一个bundle。函数的实现基本上是ffi的标准写法。类型要和jni中的对应。jstring就相当于Java中的String。这里函数一定要和 .h 文件中的一样。代码很简单,获取参数input.然后和Hello 拼接。最后输出。 接下来进行编译即可。拿到库文件,放倒java项目下即可运行。更详细的东西可以自己去jni-rs下面看example。 重点关注的是,java -h命令和rust中的函数如何编写。 再更新 很多人说这样做会找不到库的位置,会出现如下错误 Exception in thread “main” java.lang.UnsatisfiedLinkError: no mylib in java.library.path 和之前没有这个库的报告的错误是一样的。这是因为定位不到库,代码不知道如何去定位”mylib.so”(linux)或者”mylib.dylib”(mac)。假如使用IDEA,请Edit Configurations(就是运行按钮) 在VM Options栏目添加上如下 -Djava.library.path=/path/to/your/lib/target/debug 即指定你lib库的地址即可。注意你实际的so文件叫做libxxxxx.so,但是你在java中loadLibrary时候依然加载的是 xxxxx(你项目的名字)。 以上即可正常运行。但是如果要考虑自动化,请使用别的成套构建工具。但是从写库的角度,这样直接写起来测试即可。毕竟交付的是so文件。 进一步使用交叉编译上一节的内容并不涉及交叉编译,只是简单讲解了如何使用jni-rs库,以及我们写jni时的主要思路:从使用端去定义接口,然后在rust端实现代码。这一部分开始进入正题,如何使用rust写安卓的库。 在使用之前需要先安装rust环境,默认读者都装了。 然后安装Android环境。我们直接使用AndroidStudio即可。安装好之后,直接在上面的菜单栏找到Tools–>SDK manager–>SDK Tools。 在下面安装如下四个 1234* Android SDK Tools* NDK* CMake* LLDB 这四个一个都不能少。我的环境是MAC。CMake和LLDB之前也装的有,但是为了和NDK配套在这里又装一次。之前没装的状况下,最终交叉编译失败。NDK也可以自行下载安装,但是我建议用AndroidStudio安装,比较方便也很配套,会省去很多麻烦。 还需要注意的事情看清楚上面的Android SDK Location,下好之后需要配置这个路径。 下载好之后先export NDK路径。我使用zsh,修改.zshrc文件,加入如下代码 12export ANDROID_HOME=/Users/$USER/Library/Android/sdkexport NDK_HOME=$ANDROID_HOME/ndk/21.0.6113669 zsh语法不再说明,其实就是加载ndk环境变量。21.0.61xxxx是我目前下载的ndk的版本文件名,读者如果是别的路径或者叫别的文件名 比如:ndk-r21之类的自行修改即可。fish bash 用户自己酌情修改。 接下来创建一个目录 greeting 1mkdir greeting 这个文件夹是放后面的NDK文件的,我建议把NDK文件,rust project 甚至将来的Android Project放在一起,方案管理。 之后 12cd greetingmkdir NDK 然后执行 123${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 26 --arch arm64 --install-dir NDK/arm64${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 26 --arch arm --install-dir NDK/arm${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 26 --arch x86 --install-dir NDK/x86 执行上面三个命令行,大家应该看出来了,就是为了执行ndk tools中的make_standalone_toolchain.py脚本,然后东西放到NDK文件夹下。执行命令会出现warn! 既然不报错,假装没看到。如果不喜欢我这种文件组织形式,按照这个思路自己换路径即可。 打开NDK文件夹我们应该可以看到arm64 arm x86这三个文件夹了。 接下来我们在创建一个cargo-config.toml。touch应该不用我说。在这个文件中写入 1234567891011[target.aarch64-linux-android]ar = "<project path>/greetings/NDK/arm64/bin/aarch64-linux-android-ar"linker = "<project path>/greetings/NDK/arm64/bin/aarch64-linux-android-clang"[target.armv7-linux-androideabi]ar = "<project path>/greetings/NDK/arm/bin/arm-linux-androideabi-ar"linker = "<project path>/greetings/NDK/arm/bin/arm-linux-androideabi-clang"[target.i686-linux-android]ar = "<project path>/greetings/NDK/x86/bin/i686-linux-android-ar"linker = "<project path>/greetings/NDK/x86/bin/i686-linux-android-clang" 这里的就是项目有路径,具体点就是/Users/xxx 这种。读者自己写入自己的具体路径就可以。 然后把这个文件拷入.cargo 文件夹下的config文件中 1cp cargo-config.toml ~/.cargo/config 可以自己去 .cargo文件夹下看这个文件。需要保证ar 和 linker路径正确。接下来运行 1rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android 加入这三个target。 我们还是在greeting文件夹下创建自己的rust lib 12cargo new cargo --libmkdir android rust项目叫cargo似乎不太好,但暂时这样。后面的android文件夹是放后面的anroid项目的。这里再次建议:NDK,rust,Android这三个项目放在一个文件夹下方便管理。本例中就是放在greeting文件夹下。 然后在cargo/src/lib.rs中随便写点代码 12345678910111213use std::os::raw::{c_char};use std::ffi::{CString, CStr};#[no_mangle]pub extern fn rust_greeting(to: *const c_char) -> *mut c_char { let c_str = unsafe { CStr::from_ptr(to) }; let recipient = match c_str.to_str() { Err(_) => "there", Ok(string) => string, }; CString::new("Hello ".to_owned() + recipient).unwrap().into_raw()} 无伤大雅,随便写点。这是一段可以和c交互的代码。 然后开始在android目录下创建一个安卓项目,具体过程非本文重点,不展示。 我们在GreetingsActivity同级的文件中建立一个叫做RustGreetings.java的类。写上如下代码 12345678public class RustGreetings { private static native String greeting(final String pattern); public String sayHello(String to) { return greeting(to); }} 这个例子和第一节中的java代码作用是类似的。定义需要的native方法 greeting。下面一段是需要用的greeting methon的sayHello method。 接下来我们开始回去写rust代码 cargo/src/lib.rs 1234567891011121314151617181920212223/// Expose the JNI interface for android below#[cfg(target_os="android")]#[allow(non_snake_case)]pub mod android { extern crate jni; use super::*; use self::jni::JNIEnv; use self::jni::objects::{JClass, JString}; use self::jni::sys::{jstring}; #[no_mangle] pub unsafe extern fn Java_com_mozilla_greetings_RustGreetings_greeting(env: JNIEnv, _: JClass, java_pattern: JString) -> jstring { // Our Java companion code might pass-in "world" as a string, hence the name. let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr()); // Retake pointer so that we can use it below and allow memory to be freed when it goes out of scope. let world_ptr = CString::from_raw(world); let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!"); output.into_inner() }} 这才是需要的rust代码。至于为什么的的方法是com mozilla。因为我在创建Android项目时候用domain用了mozilla.com。总体来说这个方法名体现的就是路径+接口方法名,和第一节体现出的命名规则是一致的。如果不熟悉 建议用java -h 。从头文件中把文件名copy过来。 #[cfg(target_os=”android”)]代表的是条件编译。细心的人已经看到我们用extern crate jni。所以我们去cargo.toml加上。 Cargo.tomal需要如下 1234[target.'cfg(target_os="android")'.dependencies]jni = { version = "0.16", default-features = false }[lib]crate-type = ["dylib"] 这和前一节的示例是一样的。cfg选项依然对应条件编译。配置好之后我们可以开始编译了 1234567891011121314cargo build --target aarch64-linux-android --releasecargo build --target armv7-linux-androideabi --releasecargo build --target i686-linux-android --releasecd ../android/Greetings/app/src/mainmkdir jniLibsmkdir jniLibs/arm64mkdir jniLibs/armeabimkdir jniLibs/x86ln -s <project_path>/greetings/cargo/target/aarch64-linux-android/release/libgreetings.so jniLibs/arm64/libgreetings.soln -s <project_path>/greetings/cargo/target/armv7-linux-androideabi/release/libgreetings.so jniLibs/armeabi/libgreetings.soln -s <project_path>/greetings/cargo/target/i686-linux-android/release/libgreetings.so jniLibs/x86/libgreetings.so 这一段干了三件事情 编译到三种不同so文件。现在工具不够好,得一个一个编译. 在我们的android目录下建立jniLibs文件,熟悉安卓的话知道,这是为了放编译好的so文件的 软连接,把lib下面的so文件软连接到第二步建立好的jniLibs文件目录下。当然你直接拷贝过去用也是可以的。看我这个实例你应该明白,你要用绝对路径做symlinks。不可以用相对路径,不然找不到。 然后我们在GreetingsActivity (主Activity)里面加上如下代码 123static { System.loadLibrary("greetings"); } 强调下必须在 onCreate method 之前就加。 接下来去对应的activity-greetings.xml中随便写点界面,反正能用到刚才定义的方法即可。 然后我们再打开GreetingsActivity写上这个 123456789@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_greetings); RustGreetings g = new RustGreetings(); String r = g.sayHello("world"); ((TextView)findViewById(R.id.greetingField)).setText(r); } 就假定刚才的用的控件id为greetingField。然后运行App即可。注意他是安卓App,用模拟器或者真机。 好了,你已经开始Hello World了! 文章可以结束了","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/tags/rust/"},{"name":"Android","slug":"Android","permalink":"https://imzy.vip/tags/Android/"},{"name":"交叉编译","slug":"交叉编译","permalink":"https://imzy.vip/tags/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91/"}]},{"title":"从零构建一个SPV钱包其一","slug":"从零构建一个SPV钱包其一","date":"2020-03-03T00:20:49.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/56817/","link":"","permalink":"https://imzy.vip/posts/56817/","excerpt":"从零构建一个SPV钱包其一开篇这是如何从零开始构建一个SPV钱包的开篇文章的第一篇,按照行文习惯,先列出系列文章的目的:","text":"从零构建一个SPV钱包其一开篇这是如何从零开始构建一个SPV钱包的开篇文章的第一篇,按照行文习惯,先列出系列文章的目的: 构建一个SPV钱包,确切的说是一个BTC的SPV钱包 在区块链世界中,去中心化一直是核心话题,实际上市面上很多钱包是有后端的,或者说有后端支援。出于种种目的,他们的钱包并非完全去中心化。但是BIP37给比特币提供了一个去中心的提案。本文的全部方案均起源于BIP37 https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki BIP37是一切的起点,所以阅读本片文章的开端必然需要阅读BIP37。 准备在正式开始之前,还需要预先的准备知识。因为不是知识普及型的文章,所以不准备介绍基本概念。但是需要提供的基本的知识会提到 首先会搭建BTC节点关于如何搭建btc节点的文章很多,不列出。本文中使用过的节点版本为v0.18,其他稳定版本均可。使用环境为乌班图。一切以可以稳定运行比特币节点为主。它属于基础准备知识,并非重点。关于如何配置节点,可参考我的博文 比特币客户端设置 比特币节点远程访问 搭建节点之后,再按照以上文章的内容,达到的的目标是配置的比特币节点可以在远端访问。当然这并不是强制目的,我只是希望开发和运行比特币节点不是在一台机器上。只要搭建的比特币节点可以支持rpc访问即可。 理解比特币如何交易交易是区块链系统的核心。在搭建一个比特币节点就是为了可以在这个节点上利用rpc发起交易,让读者理解比特币交易的具体形式(确切的说是构建裸交易的具体形式)。关于如何构建交易可以参考我的博文 比特币交易的基本问题 使用比特币节点进行交易 读了以上两篇博文之后,基本可以理解如何进行比特币裸交易的构建,在以下还是特意列出核心步骤 提取UTXO 发起交易 对交易进行签名 全网广播 理解比特币如何交易之后,就知道了如何发起一个基本的交易。基本交易的方式就是按照以上的步骤进行(特指使用本身提供的cli服务或者curl进行访问),如果还需要进一步(定义进一步为使用自己的rpc代码进行交互完成交易),我自己本身也写了一个rpc服务的代码,挂在我的githu上。很简陋,但是可以用。 准备的原因有以上两方面的准备处于这个目的。其一,和比特币有关的东西都需要从搭建节点开始,虽然最后的代码中无需使用我们本地搭建的节点,以DNS探测为主,但这是基础中的基础。其二,使用SPV钱包最终也是要进行交易。我们在最开始的地方就确定最终的目标,防止在复杂的区块链钱包技术细节中迷失自己。为了达到这两个目的,我提到的文章建议阅读,如有其他疑惑也可以参考《精通比特币》一书。我很多对比特币最初的疑惑都在这本书中得到了解答。 理解基石进行以后的步骤之后,是需要明白构建SPV钱包的基石我们实现交易的形式 搭建全节点,利用全节点提供的rpc服务进行交易。也就是准备章节的第二部分所提到的方法 利用SPV节点,借助SPV节点,利用比特币协议进行交互达到交易的目的。 Rome was not built in a day理解第一点,更有助于理解第二点。为此我专门绘制了如下的图 以上的图体现了SPV体系的基本关系,对这张图提出一个简要的说明 第一部分是全节点的交易方式,全节点保存全部的交易数据,互相关联且拥有全部数据。是基本的比特币网络和基本交易形式 第二部分是我们需要关注的,SPV节点需要如何构建交易。为了清楚的说明问题,特地把wallet和spv且分开,其实我们要开发的钱包为了达到去中心化交易的目的,这两个部分是不可分割的。wallet链接一个SPV节点,SPV节点也通过p2p网络接入比特币网络。特别强调:SPV节点也需要和全节点相互链接才可以发挥正常的功能,否则spv节点数据缺失不足以完成正常功能。SPV节点发送loadfilter消息给fullNode。fullNode设置过滤器后加载过滤后的数据给SPV节点。其中用到的过滤器我布隆过滤器。他们之间的消息规范遵循比特币网络协议。此外SPV节点也需要存储完整的blockheader用于消息的验证。 比特币网络协议请参考如下 https://en.bitcoin.it/wiki/Protocol_documentation 请务必仔细研读,它对于后续的工作意义非凡。也需要反复参阅! 基础理论的准备工作先写到这里,后续的细节会成为一个系列文章。本文也会在后续有较大幅度的改动。 待续…","categories":[{"name":"技术文章","slug":"技术文章","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/"}],"tags":[{"name":"SPV钱包","slug":"SPV钱包","permalink":"https://imzy.vip/tags/SPV%E9%92%B1%E5%8C%85/"},{"name":"系列文章","slug":"系列文章","permalink":"https://imzy.vip/tags/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"}]},{"title":"rust工程实践","slug":"rust工程实践","date":"2020-02-16T21:22:27.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/10047/","link":"","permalink":"https://imzy.vip/posts/10047/","excerpt":"rust工程实践本文是rust的实践记录,主要用来记录rust写工程代码,和在tikv的talent-plan中学习到的代码常识。这里列出的是比较重要,但是不足以写一篇文章来讨论的。长期更新。以后考虑加个目录方便查询。","text":"rust工程实践本文是rust的实践记录,主要用来记录rust写工程代码,和在tikv的talent-plan中学习到的代码常识。这里列出的是比较重要,但是不足以写一篇文章来讨论的。长期更新。以后考虑加个目录方便查询。 在写lib的时候,直接建立(在src下)bin文件夹,bin文件下的东西无需在lib.rs中声明就可以使用。且bin文件夹下的rust文件无论怎样命名,在文件中只要有fn main()就可以直接运行。也无需在Cargo.toml文件中进行特殊声明。在murmel和talent-plan中已经证明这一点。 想要进行测试,请在src的同级目录下建立一个tests文件夹(注意拼写,必须是tests)。然后就可以使用cargo test命令。 [dev-dependencies] 适用于tests example benchmarks。正式构建的时候不会用到这个 注重测试,因为测试定义了什么是正确行为。所以阅读源码也要看测试 Re-export技巧。假定我们有一个lib kvs。目录为kvs/src/lib.rs 在lib中定义了mod kv。另有文件夹kvs/src/kv.rs。(该文件中定义KvStore struct)如何实现使用kvs::KvStore即可正确定位。(不做特殊处理,我们应该使用kvs::kv::KvStore)。正确做法是在lib中使用pub use KvStore。这种技巧叫做Re-export。导入上一级即可。(各种无法正确引入,或者需要简化路径的,全部考虑这个方法)。理论基础可参阅《深入浅出rust》的使用设施/模块管理章节。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/tags/rust/"},{"name":"笔记","slug":"笔记","permalink":"https://imzy.vip/tags/%E7%AC%94%E8%AE%B0/"}]},{"title":"记录一次烦人的编译出错问题","slug":"记录一次烦人的编译出错问题","date":"2019-12-23T18:08:35.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/21279/","link":"","permalink":"https://imzy.vip/posts/21279/","excerpt":"记录一次烦人的编译问题,因为这个过程可以能可能会对以后解决类似问题有一定的参考性,特地做个记录。","text":"记录一次烦人的编译问题,因为这个过程可以能可能会对以后解决类似问题有一定的参考性,特地做个记录。 我想做什么我之所以要做这个,是因为我想借用murmel库,在murmel基础上拓展功能。其中murmel的消息类型定义在rust-bitcoin库中。为了拓展功能,我准备先fork rust-bitcoin魔改rust-bitcoin,加我想要的消息。然后fork murmel,魔改murmel外带让murmel依赖我修改过的rust-bitcoin。 听起来有点像把大象放冰箱需要几步。是的,我先改rust-bitcoin(这个库是序列化比特币消息的),然后我修改murmel,让他依赖改过的rust-bitcoin。最后我得到一个可以用的SPV节点。完工! 我遇到了什么问题先魔改rust-bitcoin。这个很顺利。然后fork了murmel,然后编译不过。此处应该有编译错误(但我不想列出来),总体意思是说有的类型 Hash 没有实现serde的部分trait。 我的折腾过程这个问题很奇怪,我之前git clone的版本是可以编译的,而且看提交记录这个项目这几个月并没有更新。然后编译之前备份的版本,可以编译。clone的新版本编译,又失败。那先对比下Cargo.lock文件。使用Beyond Compare对比。发现两份文件的真的不一样。问题来了,代码一样,一个可以编译一个不可以。所以先替换了lock,发现了一个事实:用之前可以编译版本的lock文件就可以正常编译,如果删出lock文件之后自动生成则无法编译。 然后开始回退版本,因为之前可以编译。猜想是版本问题,使用git log 查看版本。神奇的情况出现了,一个一个rev回退之后竟然都不可以编译。一般来说回退版本之后基本是可以找到可以编译版本的。然后查看Tag,发现只有两个Tag,直接切换到对应的Tag上竟然也不可以编译。算了,那直接去下载github上的release版本。发现还是不可以编译… 经过之前的折腾,完全不知所措。本来想的是一个版本一个版本的回退,找到一个可以编译的版本,然后查看他依赖的哪个版本的rust-bitcoin,然后把这个rust-bitcoin改成本地依赖。现在发现都不可以编译…没办法需要阅读《Cargo Book》看看有没有解决办法。毕竟他之前是可以编译的。看了一夜《Cargo Book》之后发现事实: Cargo.toml文件中version的依赖方式是依托于cartes.io的,并不是依赖github。 上传到crates.io的包一定是可以编译的。 基于以上两点,那可以考虑从crates.io上clone一个包下来进行编译。需要安装一个第三方包cargo clone。使用cargo clone拉一个murmel。发现可以编译。OK,现在问题解决一大半了。crates.io上的版本是一定可以编译的。然后再使用另一个第三方的包cargo tree去分析下可以编译版本的murmel的依赖关系。注意:这个cargo tree是依赖lock文件分析依赖关系的,必须在一个真正的项目下下去运行,加入在work-space的目录下是无法分析的。可以看到出对应的rust-bitcoin版本。接下来直接使用cargo clone去拉可以编译版本的rust-bitcoin。下载下来之后尝试下是否可以直接编译。发现rust-bitcoin直接编译没有问题。然后把murmel中的依赖指向我们可以编译的rust-bitcoin。注意:murmel和rust-bitcoin必须在同一个大的cargo项目下。否则编译murmel的时候找不到rust-bitcoin。可以使用work-space的方式去组织项目。遗憾的是这么做竟然还不行。 emmm,到这里快放弃了。不过别慌,再看看《Cargo Book》。还好,cargo book 上还有一个解决办法那就是使用Cargo.Patch。 https://doc.rust-lang.org/edition-guide/rust-2018/cargo-and-crates-io/replacing-dependencies-with-patch.html https://learnku.com/docs/cargo-book/2018/specifying-dependencies/4773 两份资料一起看。我们直接使用patch。然后又出现问题了……原来我们的项目是一个workspace下面有两个平行的项目murmel 和 rust-bitcoin。patch不能这么做。ok那调整文件结构。把rust-bitcoin调整到murmel文件夹中,和murmel的Cargo.toml一级。然后在Cargo.toml在使用如下 123456[dependencies.bitcoin]version = "0.21"features = ["serde"][patch.crates-io]bitcoin = { path = "bitcoin" } patch这一行是新添加的。path代表路径,从crates.io上下下来的rust-bitcoin文件夹名称为“bitcoin”。 编译通过。 然后murmel和rust-bitcoin都在本地了。想改哪个该哪个。完工。 最后,还有改进余地。把修改过rust-bitcoin上传到github,然后在patch中依赖{git=”xxxxx.git”,rev = “xxxxx”}。更适合代码管理,不用依赖本地了。我因为要频繁修改代码,所以先不依赖github上的版本。 总结知识点 Cargo中依赖方式有三种 依赖本地 依赖git {git = “xxx”, rev = “xxxx”} 依赖crates.io上的包。 最终解决的办法是 安装Cargo tree 和 Cargo Clone 从crates上clone一个可以编译的murmel 使用cargo tree看依赖关系。(其实直接看Cargo.toml也可以看出来,问题在于看起来都没差别但是就是编译不过,以前的备份又确实可以编译过) 使用cargo clone提取能编译版本的rust-bitcoin 把murmel依赖的能编译的版本的rust-bitcoin放倒murmel目录下 使用cargo patch打补丁,使murmel依赖的rust-bitcoin指向本地。 编译通过了,现在murmel和rust-bitcoin都在本地,可以自由更改了。 可以把自己改过的rust-bitcoin上传到github。Patch指向github从版本管理角度更好点。 穷举法解决问题。要是还不行,那我真没办法了。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/tags/rust/"},{"name":"问题经验","slug":"问题经验","permalink":"https://imzy.vip/tags/%E9%97%AE%E9%A2%98%E7%BB%8F%E9%AA%8C/"}]},{"title":"SPV节点获取merkerblock数据","slug":"SPV节点获取merkerblock数据","date":"2019-12-10T14:54:22.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/2926/","link":"","permalink":"https://imzy.vip/posts/2926/","excerpt":"在之前的文章中提到了SPV节点,一直说要写文章说明是什么是比特币SPV节点。网上有很多文章来描述这个问题,我之前也写过相关的文章,有兴趣的话可以回去查阅相关问题。简单来说,SPV节点最主要的特点就是:只存储头信息(BlockHeader)。所以他做到了存储数据规模大幅减少,只有完整数据的千分之一的水平。所以SPV节点适合在存储有限的设备上运行,比如手机客户端。但是本文的重点不是重复讲述SPV节点的概念的,而是换一个角度,从比特币网络协议入手,描述如何从全节点下载SPV所需的数据到节点上(SPV节点上)。这个所需的的信息,就是merkerblock。","text":"在之前的文章中提到了SPV节点,一直说要写文章说明是什么是比特币SPV节点。网上有很多文章来描述这个问题,我之前也写过相关的文章,有兴趣的话可以回去查阅相关问题。简单来说,SPV节点最主要的特点就是:只存储头信息(BlockHeader)。所以他做到了存储数据规模大幅减少,只有完整数据的千分之一的水平。所以SPV节点适合在存储有限的设备上运行,比如手机客户端。但是本文的重点不是重复讲述SPV节点的概念的,而是换一个角度,从比特币网络协议入手,描述如何从全节点下载SPV所需的数据到节点上(SPV节点上)。这个所需的的信息,就是merkerblock。 本文的关键字是:”SPV节点”,”比特币网络协议”,”BIP47” 交易的基本问题在探讨如何和比特币网络进行信息交互之前先解释一个基本问题 实现比特币交易的形式有哪些? 虽然如此描述这个问题并不准确,但我还是按照自己的理解去解答这个问题,同时参照以上的图片来辅助说明。实现交易的形式有两类 借助于比特币全节点 借助于比特币的SPV节点 对于1,很明确。比特全节点有区块链完整的信息。获取任何交易信息,发起交易和广播不在话下。 重点需要解释的是2。借助于比特币的SPV节点。之前的文章提到过,SPV节点需要根据merkel root来验证交易的存在(注意用词,是验证交易的存在,反之是不行的)。那把这个问题提前一步,变成SPV节点如何获取自己所需的merkle root数据,这个图就是来描述这个问题的 一个SPV的体系如图所示,wallet连接一个SPV的节点,因为SPV节点不存在完整的数据,所以SPV必然连接其他的完整的节点以获取自己所需要的数据。节点Peers 指代的就是其他的Full Node。 实际情况中,如果要实现一个用户意义上的钱包(这个意义指:用户用这样一个App就可以实现比特币交易)wallet 和 SPV节点必然紧密相连。这里为了说明情况,所以特意分开。SPV节点和其他的Full Node之间是建立P2P网络的,他们直接交互就需要借助比特币网络协议。 本文假定SPV Node已经发现到了其他的Full Node(指已经知道了其他节点的IP),节点发现和建立连接也是比特币网络协议中的专门话题,本文为了简化,暂不对其进行探讨。以后有机会再出一篇文章。当节点建立连接之后,SPV节点将向节点发送LoadFilter 消息,这条消息会设置和布隆过滤器有关的参数,比如filter,需要的的Hash Function个数等。Full Node会根据设置调整自己的布隆过滤器。接下来,SPV节点发送getdata消息给Full Node。因为第一次设置了布隆过滤器,所以全节点会利用布隆过滤器过滤掉无关信息,把SPV感兴趣的信息发送回来。这样就形成了一次完整的信息交互。 以上的描述中,需要明确一个小的问题 Bloom Filtter在哪里? Bloom Filtter在全节点上。一个支持布隆过滤器的节点,需要自己实现和Bloom Filtter 有关的方法。这样它就可以根据其他节点回传回来的loadfiltter 消息来设置。等到SPV节点需要getdata的时刻,利用之前已经设置好的Bloom filtter来过滤信息。发送merker root回去。 以上的内容,希望参考bip47的文档来理解 如何从比特币P2P网络中获取需要的数据以上的内容介绍过之后,其实其实如何从比特币P2P网络中获取信息就已经说明了,不过为了文章的完整性,按照之前的惯例列出步骤。以下步骤假定已经确定对等节点的ip,且我们需要的数据为 merkerblock SPV节点向Full Node发送 version 消息 等待对等节点发送 verack 消息之后,SPV节点向Full Node发送 verack 消息 SPV节点发送 loadfilter 消息给Full Node SPV节点发送 getdata 消息给Full Node Full Node 回传 merkerblock 消息给SPV节点 其中前两个节点为握手的过程,必须有前两个步骤才可以正常的发送消息。比特币网络信息的交互全部依赖这种Message的传递。至于消息具体该怎么样请参考比特币网络协议的官方wiki和bitcoin-refrence。因为实际发送的数据和比特币网络协议wiki数据有出入,所以这两份材料要对照进行。另外比特币节点之间建立的是TCP链接。如果需要自己写代码的话,可以使用tokios这样的库(parity-bitcoin也借助这个库)。因为rust写起来相对麻烦,下面给出一个python的实例,体现这个过程 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061from time import sleepfrom hashlib import sha256import structimport sysnetwork_string = "f9beb4d9".decode("hex") # Mainnetdef send(msg,payload): ## Command is ASCII text, null padded to 12 bytes command = msg + ( ( 12 - len(msg) ) * "\\00" ) ## Payload length is a uint32_t payload_raw = payload.decode("hex") payload_len = struct.pack("I", len(payload_raw)) ## Checksum is first 4 bytes of SHA256(SHA256(<payload>)) checksum = sha256(sha256(payload_raw).digest()).digest()[:4] sys.stdout.write( network_string + command + payload_len + checksum + payload_raw ) sys.stdout.flush()## Create a version messagesend("version", "71110100" # ........................ Protocol Version: 70001 + "0000000000000000" # ................ Services: Headers Only (SPV) + "c6925e5400000000" # ................ Time: 1415484102 + "00000000000000000000000000000000" + "0000ffff7f000001208d" # ............ Receiver IP Address/Port + "00000000000000000000000000000000" + "0000ffff7f000001208d" # ............ Sender IP Address/Port + "0000000000000000" # ................ Nonce (not used here) + "1b" # .............................. Bytes in version string + "2f426974636f696e2e6f726720457861" + "6d706c653a302e392e332f" # .......... Version string + "93050500" # ........................ Starting block height: 329107 + "00" # .............................. Relay transactions: false)sleep(1)send("verack", "")send("filterload", "02" # ........ Filter bytes: 2 + "b50f" # ....... Filter: 1010 1101 1111 0000 + "0b000000" # ... nHashFuncs: 11 + "00000000" # ... nTweak: 0/none + "00" # ......... nFlags: BLOOM_UPDATE_NONE)send("getdata", "01" # ................................. Number of inventories: 1 + "03000000" # ........................... Inventory type: filtered block + "a4deb66c0d726b0aefb03ed51be407fb" + "ad7331c6e8f9eef231b7000000000000" # ... Block header hash) 简单说明,网络的协议中的数据为hex小端编码。消息遵循的形式为messageHeader+payload。其中payload为具体消息的形式。比特币网络协议中的任何消息都遵循这个格式。 1python get-merkle.py | nc localhost 8333 | hd 按以上格式运行即可。(假定节点搭建在本地,采用默认端口)。nc为netcat,hd为hexdump工具。 给出参考的资料 https://en.bitcoin.it/wiki/Protocol_documentation#filterload.2C_filteradd.2C_filterclear.2C_merkleblock https://bitcoin.org/en/developer-reference#message-headers","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"SPV节点","slug":"SPV节点","permalink":"https://imzy.vip/tags/SPV%E8%8A%82%E7%82%B9/"},{"name":"比特币网络协议","slug":"比特币网络协议","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/"}]},{"title":"如何给过长数组实现Debug","slug":"如何给过长数组实现Debug","date":"2019-11-26T18:34:18.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/64038/","link":"","permalink":"https://imzy.vip/posts/64038/","excerpt":"更新:rust在2021年实现了const generics。以下的问题不会存在了。 在rust中,我们可以很方便的用Derive给结构体实现Debug宏(是编译器自动实现的),但是编译器给数组实现的Debug只有长度在32以下的,要是超过32位就得自己实现了。所以出现了本文的问题","text":"更新:rust在2021年实现了const generics。以下的问题不会存在了。 在rust中,我们可以很方便的用Derive给结构体实现Debug宏(是编译器自动实现的),但是编译器给数组实现的Debug只有长度在32以下的,要是超过32位就得自己实现了。所以出现了本文的问题 如何给过长数组手动实现Debug Trait。一番尝试之后,发现可以这样 1234567891011121314151617use std::fmt;struct Array<T> { data: [T; 1024]}impl<T: fmt::Debug> fmt::Debug for Array<T> { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { self.data[..].fmt(formatter) }}fn main() { let array = Array { data: [0u8; 1024] }; println!("{:?}", array);} 注意:如果直接考虑给[T; 1024]实现Debug是不行的,因为违反了孤儿规则。Debug Tarit和[T;1204]都没有在本crate中定义。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/tags/rust/"}]},{"title":"周报,软件构建和职业发展","slug":"周报,软件构建和职业发展","date":"2019-11-18T16:40:50.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/51833/","link":"","permalink":"https://imzy.vip/posts/51833/","excerpt":"吾日三省吾身 ——《论语·学而》 距离上次频繁发布文章,已经过去一个多月了。这一个多月以来,我非常认真的研究关于取款链交易和SPV节点相关的问题。今日又听闻leader关于自己软件生涯的反思和总结。leader开发生涯漫长,反思亦有重大价值,故作文。","text":"吾日三省吾身 ——《论语·学而》 距离上次频繁发布文章,已经过去一个多月了。这一个多月以来,我非常认真的研究关于取款链交易和SPV节点相关的问题。今日又听闻leader关于自己软件生涯的反思和总结。leader开发生涯漫长,反思亦有重大价值,故作文。 在软件开发过程中,几乎每家公司都有写周报的要求,管理粒度更细化的公司还要要求日报。要求详细回报每日、每周的工作情况。周报从公司和项目leader的角度来说,他可以监控开发人员的开发情况,对于KPI管理也有较大作用。但是多数时候我们是怎么对待周报的呢:觉得周报是对公司的交代,对领导交代?每周的周报在周末快下班的时候草草了事?我想这是普遍现象。 其实周报最大的意义是关乎我们自身的。周报的最大意义是对于我们工作的总结和预估。不知道读者读过柳比歇夫那本《奇特的一生》吗?那本的书所宣扬的思想并不复杂,就是精确地统计自己每日工作时间。然后每日总结,看自己那些时间是低效浪费的,加以改进。时间统计法最大的原则就是诚实。吃饭喝水,哪怕是工作时间划水也要如实记录。Timelog和周报的区别就是,Timelog完全是对自己负责,写周报的时候大部分都想草草了事。诚实的对待自己,才有修正的余地。通过日复一日的精确统计和反思,柳比歇夫爆发了惊人的工作效率。如果周报能真诚面对自己,对自己负责,也比想着对公司负责拥有更大的意义。实际上,每日报告更高级的形式是先做规划,一日结束之后再对自己进行总结。所谓吾日三省吾身,这就是对我们软件开发的反省。 可以预见,这种形式的开头,必然是痛苦的。因为我们每日开头做的计划,再每日结束的时候是很难完成的。毕竟对于预见性计划,开始的时候我们都是新手,精准规划必然是精英的技能,是反复练习的结果。新手阶段每日反省时刻,如果计划没有完成必然痛苦。因为我们看不到结果,在短期中也感受不到进步。不过我始终认为,所有对我们有意义的事情,开头必然是痛苦的。其实在过去一年的rust学习中,我也在适当的引导下尝试这个方法,每周列出自己学习计划,然后每日学习,学习之后进行反思。再周末的时候再进行一次大反思,根据小反思和大反思去改进下一周的计划。产生的结果就是,经过数个月的学习,我的rust基本达到了可用的地步。 第三个观点是:所有看似轻松地过程,必然有反复的枯燥练习。以上的周报书写过程,按照周leader的说法,如果我们能每日坚持。必然在软件构建和职业发展上有巨大进步。因为人的一般认知限定了,我们很难有预估未来的能力,除非反复练习,每日坚持。leader提到,如果我们反复练习这种,预估,实践,反思的三步走模式就能在更长的时间单位中做到更好的预估。做到任何事情(主要是写代码)都能做到心中有蓝图,我们就能完成规模更大,复杂度更高的任务。试想如果团队中的每个人都有这种觉悟和能力,我们必然工作开心而高效。因为我们的思路不断清晰,我们清晰的之后自己干过什么,我们将要做什么。开发中的迷雾将在我们的探索中不断消散,直至成功。 共勉","categories":[{"name":"感悟","slug":"感悟","permalink":"https://imzy.vip/categories/%E6%84%9F%E6%82%9F/"}],"tags":[{"name":"思考","slug":"思考","permalink":"https://imzy.vip/tags/%E6%80%9D%E8%80%83/"}]},{"title":"Rust关于数据溢出安全","slug":"Rust关于数据溢出安全","date":"2019-11-18T15:44:09.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/36864/","link":"","permalink":"https://imzy.vip/posts/36864/","excerpt":"在4个多月之前,也就是19年的7月份。在大佬的推荐下,有幸拿到了今日头条的面试机会。在漫长的面试过程中,头条的面试官态度热情,问的问题兼具广度和深度。再很多问题回答的一塌糊涂的条件下,也让我面试了长达五小时。特别的最后的面试官,态度友好且诚恳,对我的诸多不足之处提出委婉的建议。有这样的面试体验是非常棒让我获益良多,在此表达我的感谢之情。不过本篇文章不是讲述面试经验或者体会的,是讲面试中被反复提到的问题:","text":"在4个多月之前,也就是19年的7月份。在大佬的推荐下,有幸拿到了今日头条的面试机会。在漫长的面试过程中,头条的面试官态度热情,问的问题兼具广度和深度。再很多问题回答的一塌糊涂的条件下,也让我面试了长达五小时。特别的最后的面试官,态度友好且诚恳,对我的诸多不足之处提出委婉的建议。有这样的面试体验是非常棒让我获益良多,在此表达我的感谢之情。不过本篇文章不是讲述面试经验或者体会的,是讲面试中被反复提到的问题: Rust数据计算溢出怎么办? 因为面试的是cpp/rust相关的岗位,面试官提出这个问题之后我是很懵的。以为之前完全没考虑过这个问题。Rust号称安全,那么他是如何处理这个问题的呢。当时我的回答是:换用更大的数据类型。其实当时这个回答说出口,就觉得完全不对,毕竟int32之上还有int64,无论用多大的数据类型,计算依旧可能溢出。 我想今天我可以很好的回答这个问题。在c语言中,无符号整数完全不会溢出(overflow),因为数据一旦超过上限,则自动舍弃高位数据。对于有符号数,一旦超过上限,则标准这是ub。Rust号称安全,从设计上就要尽可能的避免ub,对于数据溢出,也算是语言必须面对的bug。rust处理这个bug分为以下两种情况(默认情况,先不谈编译参数的调整) debug模式下,编译器会自动插入溢出检查,一旦overflow则立即panic。 release模式下,编译器不进行溢出检查,一旦overflow则舍弃高位 如果我们在编译时调整编译参数 rustc -C overflow-checks=no overflow-checks=yes 或者 no,可以打开或关闭编译器的溢出检查。这样的话,无论是debug还是release模式都可以有统一的溢出检查设置。 当然,我们需要的不仅仅是这个,如果需要更细力度的溢出处理,我们可以使用以下函数 123check_*saturating_*warpping_* 查阅API文档可知 check_*函数返回Option,一旦发生溢出则返回None saturating_*系列函数返回类型是整数,如果溢出,则给出该类型可表示范围的“最大/最小”值 wrapping_*系列函数则是直接抛弃已经溢出的最高位,将剩下的部分返回 如果你再进一步仔细查阅Rust源码,发现源码中大量的数据运算都采用这三个函数。当然了,如果你不使用这三类函数,你还可以使用std::num::Warping的类型,他重载了基本运算符。溢出直接截断。这类包裹的用法,不受编译器溢出参数的影响,永远直接截断高位,不会panic. 以上方法就是rust处理溢出的所有情况。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"Rust","slug":"Rust","permalink":"https://imzy.vip/tags/Rust/"},{"name":"数据溢出","slug":"数据溢出","permalink":"https://imzy.vip/tags/%E6%95%B0%E6%8D%AE%E6%BA%A2%E5%87%BA/"}]},{"title":"构建离线交易","slug":"构建离线交易","date":"2019-10-10T17:54:15.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/46484/","link":"","permalink":"https://imzy.vip/posts/46484/","excerpt":"本文是在之前的复杂交易的基础上进行的,离线签名主要是为了满足以下场景","text":"本文是在之前的复杂交易的基础上进行的,离线签名主要是为了满足以下场景 我们都知道,正常的交易需要确认6次以后才可以被视作可信(confirm)交易。当我们需要从A到B再到C连续交易时,如果离线签名不存在,则必须在A到B的交易被确认后,再发起一次B到C的交易。而离线签名可以使我们做到A到B的交易在广播之前就立刻发起一个下一步交易,之后再进行广播。 按照之前惯例,列出离线签名的主干步骤 发起第一步交易,完成对交易的签名 在第一步交易未广播之前,发起第二笔交易 依次广播第一次交易和第二交易,完成整个交易过程 以上的交易中,提取UXTO,发起交易,签名的步骤和之前列出的交易类型是一致的,不可省略。下面列出发起离线签名的步骤,其中18步之前和前文的构建复杂交易是一致的,之后的步骤是发起离线签名的过程 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762771. 启动测试链bitcoind -printtoconsole -regtest2. 查询链信息bitcoin-cli -regtest -getinfo3. 生成地址 2MvFP1Mf8Y7v3VsoUtPi4FqA39eNySyKhK9bitcoin-cli -regtest getnewaddress4. 导出私钥备用 cUBZgraadC4HdjCf5px1QT9cLENgF6RPGkXfqaS7TivpCtAUctMbbitcoin-cli -regtest dumpprivkey 2MvFP1Mf8Y7v3VsoUtPi4FqA39eNySyKhK95. 给这个地址挖矿 101块bitcoin-cli -regtest generatetoaddress 101 2MvFP1Mf8Y7v3VsoUtPi4FqA39eNySyKhK96. 生成第二个地址 2N8nqc8HxKaziqbJR3T9KotZkb5unYq7R5nbitcoin-cli -regtest getnewaddress7. 导出私钥 cQne7yVqswhrC43N3DushL6XzzejHBkBwzrEM8ydeRcR7MTBmPV9bitcoin-cli -regtest dumpprivkey 2N8nqc8HxKaziqbJR3T9KotZkb5unYq7R5n8. 挖矿 102块bitcoin-cli -regtest generatetoaddress 102 2N8nqc8HxKaziqbJR3T9KotZkb5unYq7R5n9. 生成地址作为输出 2NDkJxccs9Wd6cctvKSBtfk2Nza2EeavS1Ybitcoin-cli -regtest getnewaddress10. 继续生成 2MxXvWg5KqLUHWQfm4z8rZhGj16aAqPWF6ybitcoin-cli -regtest getnewaddress11. 提取UXTObitcoin-cli -regtest listunspent 6 9999999 "[\\"2MvFP1Mf8Y7v3VsoUtPi4FqA39eNySyKhK9\\"]"{ "txid": "eef9c597e33f507bc8fd8a9e883bfd5843986b5cc4fb56ff688735f44b2e8539", "vout": 0, "address": "2MvFP1Mf8Y7v3VsoUtPi4FqA39eNySyKhK9", "label": "", "redeemScript": "001482d55d9dff1765974f91aa49ca8390bc3da3d761", "scriptPubKey": "a91420efe7d679e1ebfa932860265e6ae69ac0087b3987", "amount": 50.00000000, "confirmations": 182, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/29']02c7a49c3f29e63cf274819490e6e0bdc115756397ca60a86091f822286f5e9a88))#x6wqjynz", "safe": true }, { "txid": "33043fc71a74c6436f5672696e8bf0c4b809fd72f5d69d698bf406415a8d6f3b", "vout": 0, "address": "2MvFP1Mf8Y7v3VsoUtPi4FqA39eNySyKhK9", "label": "", "redeemScript": "001482d55d9dff1765974f91aa49ca8390bc3da3d761", "scriptPubKey": "a91420efe7d679e1ebfa932860265e6ae69ac0087b3987", "amount": 50.00000000, "confirmations": 196, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/29']02c7a49c3f29e63cf274819490e6e0bdc115756397ca60a86091f822286f5e9a88))#x6wqjynz", "safe": true }14. 提取UTXO 只列举一小部分bitcoin-cli -regtest listunspent 6 9999999 "[\\"2N8nqc8HxKaziqbJR3T9KotZkb5unYq7R5n\\"]"[ { "txid": "be12c06ce23a174d29a507b09a5bc86ef38fc0ad5467e2083c9d371f0928a5d5", "vout": 0, "address": "2N8nqc8HxKaziqbJR3T9KotZkb5unYq7R5n", "label": "", "redeemScript": "0014044267290a1ed9e1b363bfdec43aac1df8f85bfb", "scriptPubKey": "a914aa848392f25d6672385271ef62b80fc03ced47d287", "amount": 50.00000000, "confirmations": 101, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/30']030541bdfff7b3e4deb1e0addf5715cbbb13407e7b06a9793a1be36233b7d49fdc))#px0ff4zh", "safe": true }, { "txid": "a3ef346a4f86ea82ac4b78c2b60f5be3b6c2b21ab0fbcecdfd87acc3b5c439e2", "vout": 0, "address": "2N8nqc8HxKaziqbJR3T9KotZkb5unYq7R5n", "label": "", "redeemScript": "0014044267290a1ed9e1b363bfdec43aac1df8f85bfb", "scriptPubKey": "a914aa848392f25d6672385271ef62b80fc03ced47d287", "amount": 50.00000000, "confirmations": 102, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/30']030541bdfff7b3e4deb1e0addf5715cbbb13407e7b06a9793a1be36233b7d49fdc))#px0ff4zh", "safe": true }] 第一组 txid: eef9c597e33f507bc8fd8a9e883bfd5843986b5cc4fb56ff688735f44b2e8539 vout: 0 address: 2MvFP1Mf8Y7v3VsoUtPi4FqA39eNySyKhK9 privkey: cUBZgraadC4HdjCf5px1QT9cLENgF6RPGkXfqaS7TivpCtAUctMb 第二组: txid: be12c06ce23a174d29a507b09a5bc86ef38fc0ad5467e2083c9d371f0928a5d5 vout: 0 address: 2N8nqc8HxKaziqbJR3T9KotZkb5unYq7R5n privkey: cQne7yVqswhrC43N3DushL6XzzejHBkBwzrEM8ydeRcR7MTBmPV9 目标地址 2NDkJxccs9Wd6cctvKSBtfk2Nza2EeavS1Y 2MxXvWg5KqLUHWQfm4z8rZhGj16aAqPWF6y15 构建交易 给每个目标49.99\b个比特币bitcoin-cli -regtest createrawtransaction ''' [ { "txid": "eef9c597e33f507bc8fd8a9e883bfd5843986b5cc4fb56ff688735f44b2e8539", "vout": 0 }, { "txid": "be12c06ce23a174d29a507b09a5bc86ef38fc0ad5467e2083c9d371f0928a5d5", "vout": 0 } ] ''' ''' { "2NDkJxccs9Wd6cctvKSBtfk2Nza2EeavS1Y": 49.99, "2MxXvWg5KqLUHWQfm4z8rZhGj16aAqPWF6y": 49.99 }'''020000000239852e4bf4358768ff56fbc45c6b984358fd3b889e8afdc87b503fe397c5f9ee0000000000ffffffffd5a528091f379d3c08e26754adc08ff36ec85b9ab007a5294d173ae26cc012be0000000000ffffffff02c0aff6290100000017a914e0e2ba743a50d6c51176cf4006a73b07c29df3cf87c0aff6290100000017a9143a011cad3e72170448f61524c490ce1ec3c7e8a4870000000016. 现在一次进行签名 这里使用私钥进行签名bitcoin-cli -regtest signrawtransactionwithkey "020000000239852e4bf4358768ff56fbc45c6b984358fd3b889e8afdc87b503fe397c5f9ee0000000000ffffffffd5a528091f379d3c08e26754adc08ff36ec85b9ab007a5294d173ae26cc012be0000000000ffffffff02c0aff6290100000017a914e0e2ba743a50d6c51176cf4006a73b07c29df3cf87c0aff6290100000017a9143a011cad3e72170448f61524c490ce1ec3c7e8a48700000000" "[\\"cUBZgraadC4HdjCf5px1QT9cLENgF6RPGkXfqaS7TivpCtAUctMb\\"]"{ "hex": "0200000000010239852e4bf4358768ff56fbc45c6b984358fd3b889e8afdc87b503fe397c5f9ee000000001716001482d55d9dff1765974f91aa49ca8390bc3da3d761ffffffffd5a528091f379d3c08e26754adc08ff36ec85b9ab007a5294d173ae26cc012be0000000000ffffffff02c0aff6290100000017a914e0e2ba743a50d6c51176cf4006a73b07c29df3cf87c0aff6290100000017a9143a011cad3e72170448f61524c490ce1ec3c7e8a48702473044022074136f51a9f5dfc5b27e4e0b2a5b576f895f3e872f16bb74441b921ed670f7dd0220276d9f4fc40a1c0349008ae82135651077932010b5e9077bf62f8bc0b9142c33012102c7a49c3f29e63cf274819490e6e0bdc115756397ca60a86091f822286f5e9a880000000000", "complete": false, "errors": [ { "txid": "be12c06ce23a174d29a507b09a5bc86ef38fc0ad5467e2083c9d371f0928a5d5", "vout": 0, "witness": [ ], "scriptSig": "", "sequence": 4294967295, "error": "Unable to sign input, invalid stack size (possibly missing key)" } ]}17 2签bitcoin-cli -regtest signrawtransactionwithkey "0200000000010239852e4bf4358768ff56fbc45c6b984358fd3b889e8afdc87b503fe397c5f9ee000000001716001482d55d9dff1765974f91aa49ca8390bc3da3d761ffffffffd5a528091f379d3c08e26754adc08ff36ec85b9ab007a5294d173ae26cc012be0000000000ffffffff02c0aff6290100000017a914e0e2ba743a50d6c51176cf4006a73b07c29df3cf87c0aff6290100000017a9143a011cad3e72170448f61524c490ce1ec3c7e8a48702473044022074136f51a9f5dfc5b27e4e0b2a5b576f895f3e872f16bb74441b921ed670f7dd0220276d9f4fc40a1c0349008ae82135651077932010b5e9077bf62f8bc0b9142c33012102c7a49c3f29e63cf274819490e6e0bdc115756397ca60a86091f822286f5e9a880000000000" "[\\"cQne7yVqswhrC43N3DushL6XzzejHBkBwzrEM8ydeRcR7MTBmPV9\\"]"{ "hex": "0200000000010239852e4bf4358768ff56fbc45c6b984358fd3b889e8afdc87b503fe397c5f9ee000000001716001482d55d9dff1765974f91aa49ca8390bc3da3d761ffffffffd5a528091f379d3c08e26754adc08ff36ec85b9ab007a5294d173ae26cc012be0000000017160014044267290a1ed9e1b363bfdec43aac1df8f85bfbffffffff02c0aff6290100000017a914e0e2ba743a50d6c51176cf4006a73b07c29df3cf87c0aff6290100000017a9143a011cad3e72170448f61524c490ce1ec3c7e8a48702473044022074136f51a9f5dfc5b27e4e0b2a5b576f895f3e872f16bb74441b921ed670f7dd0220276d9f4fc40a1c0349008ae82135651077932010b5e9077bf62f8bc0b9142c33012102c7a49c3f29e63cf274819490e6e0bdc115756397ca60a86091f822286f5e9a8802473044022053aaaa8f122a8430342d274fdd57ea8dc746cce269e099d78ce2778656406c48022003e3788c5694ce17fcd842b6685f44afe6a7151dcfa214d4198d5503134d2d210121030541bdfff7b3e4deb1e0addf5715cbbb13407e7b06a9793a1be36233b7d49fdc00000000", "complete": true}18 接下来进行离线签署1. decodebitcoin-cli -regtest decoderawtransaction 0200000000010239852e4bf4358768ff56fbc45c6b984358fd3b889e8afdc87b503fe397c5f9ee000000001716001482d55d9dff1765974f91aa49ca8390bc3da3d761ffffffffd5a528091f379d3c08e26754adc08ff36ec85b9ab007a5294d173ae26cc012be0000000017160014044267290a1ed9e1b363bfdec43aac1df8f85bfbffffffff02c0aff6290100000017a914e0e2ba743a50d6c51176cf4006a73b07c29df3cf87c0aff6290100000017a9143a011cad3e72170448f61524c490ce1ec3c7e8a48702473044022074136f51a9f5dfc5b27e4e0b2a5b576f895f3e872f16bb74441b921ed670f7dd0220276d9f4fc40a1c0349008ae82135651077932010b5e9077bf62f8bc0b9142c33012102c7a49c3f29e63cf274819490e6e0bdc115756397ca60a86091f822286f5e9a8802473044022053aaaa8f122a8430342d274fdd57ea8dc746cce269e099d78ce2778656406c48022003e3788c5694ce17fcd842b6685f44afe6a7151dcfa214d4198d5503134d2d210121030541bdfff7b3e4deb1e0addf5715cbbb13407e7b06a9793a1be36233b7d49fdc00000000{ "txid": "593bc8b6149fc64d31d70d58d365764fc362fd2aecf6adabb6c2be3964682389", "hash": "47d4757e05dffdd2a24ca98134ee4aa87d00521ac5237cdd8cbfbf475b6ef870", "version": 2, "size": 418, "vsize": 256, "weight": 1024, "locktime": 0, "vin": [ { "txid": "eef9c597e33f507bc8fd8a9e883bfd5843986b5cc4fb56ff688735f44b2e8539", "vout": 0, "scriptSig": { "asm": "001482d55d9dff1765974f91aa49ca8390bc3da3d761", "hex": "16001482d55d9dff1765974f91aa49ca8390bc3da3d761" }, "txinwitness": [ "3044022074136f51a9f5dfc5b27e4e0b2a5b576f895f3e872f16bb74441b921ed670f7dd0220276d9f4fc40a1c0349008ae82135651077932010b5e9077bf62f8bc0b9142c3301", "02c7a49c3f29e63cf274819490e6e0bdc115756397ca60a86091f822286f5e9a88" ], "sequence": 4294967295 }, { "txid": "be12c06ce23a174d29a507b09a5bc86ef38fc0ad5467e2083c9d371f0928a5d5", "vout": 0, "scriptSig": { "asm": "0014044267290a1ed9e1b363bfdec43aac1df8f85bfb", "hex": "160014044267290a1ed9e1b363bfdec43aac1df8f85bfb" }, "txinwitness": [ "3044022053aaaa8f122a8430342d274fdd57ea8dc746cce269e099d78ce2778656406c48022003e3788c5694ce17fcd842b6685f44afe6a7151dcfa214d4198d5503134d2d2101", "030541bdfff7b3e4deb1e0addf5715cbbb13407e7b06a9793a1be36233b7d49fdc" ], "sequence": 4294967295 } ], "vout": [ { "value": 49.99000000, "n": 0, "scriptPubKey": { "asm": "OP_HASH160 e0e2ba743a50d6c51176cf4006a73b07c29df3cf OP_EQUAL", "hex": "a914e0e2ba743a50d6c51176cf4006a73b07c29df3cf87", "reqSigs": 1, "type": "scripthash", "addresses": [ "2NDkJxccs9Wd6cctvKSBtfk2Nza2EeavS1Y" ] } }, { "value": 49.99000000, "n": 1, "scriptPubKey": { "asm": "OP_HASH160 3a011cad3e72170448f61524c490ce1ec3c7e8a4 OP_EQUAL", "hex": "a9143a011cad3e72170448f61524c490ce1ec3c7e8a487", "reqSigs": 1, "type": "scripthash", "addresses": [ "2MxXvWg5KqLUHWQfm4z8rZhGj16aAqPWF6y" ] } } ]}从以上信息中提取一组有效信息 从vout中选择第二组数据UTXO_TXID = 593bc8b6149fc64d31d70d58d365764fc362fd2aecf6adabb6c2be3964682389UTXO_VOUT = 1UTXO_VALUE = 49.99000000UTXO_OUTPUT_SCRIPT = a9143a011cad3e72170448f61524c490ce1ec3c7e8a4872. 生成一个新的目标地址 2N7hMdx8XoKMyBJd4PLXZUvEa3szWLK7c4jbitcoin-cli -regtest getnewaddress3. 从以上的有效信息中构建一笔交易给新的地址49.98个比特币bitcoin-cli -regtest createrawtransaction ''' [ { "txid": "593bc8b6149fc64d31d70d58d365764fc362fd2aecf6adabb6c2be3964682389", "vout": '1' } ] ''' ''' { "2N7hMdx8XoKMyBJd4PLXZUvEa3szWLK7c4j": 49.98 }'''02000000018923686439bec2b6abadf6ec2afd62c34f7665d3580dd7314dc69f14b6c83b590100000000ffffffff01806de7290100000017a9149e82f61edb11d83f62a60368868a7a16bcf55a8887000000004. 离线签名bitcoin-cli -regtest signrawtransactionwithwallet 02000000018923686439bec2b6abadf6ec2afd62c34f7665d3580dd7314dc69f14b6c83b590100000000ffffffff01806de7290100000017a9149e82f61edb11d83f62a60368868a7a16bcf55a888700000000 '''{ "hex": "020000000001018923686439bec2b6abadf6ec2afd62c34f7665d3580dd7314dc69f14b6c83b59010000001716001490c66a16ddd8405227e11efc87f7b93fce151373ffffffff01806de7290100000017a9149e82f61edb11d83f62a60368868a7a16bcf55a88870247304402203665a815716ed7c99a627acf896806101e851a9122a05a9dd337a7c70792800d022070b1a1c1947bbb81aff11e966392a4b05e3d4b2785a8170f70b4d0250f960e72012103d678d4c116ea3bff2b18760a45d86fd19692c213b217f88a6815006387290cfc00000000", "complete": true}5. 广播之前的交易 593bc8b6149fc64d31d70d58d365764fc362fd2aecf6adabb6c2be3964682389bitcoin-cli -regtest sendrawtransaction 0200000000010239852e4bf4358768ff56fbc45c6b984358fd3b889e8afdc87b503fe397c5f9ee000000001716001482d55d9dff1765974f91aa49ca8390bc3da3d761ffffffffd5a528091f379d3c08e26754adc08ff36ec85b9ab007a5294d173ae26cc012be0000000017160014044267290a1ed9e1b363bfdec43aac1df8f85bfbffffffff02c0aff6290100000017a914e0e2ba743a50d6c51176cf4006a73b07c29df3cf87c0aff6290100000017a9143a011cad3e72170448f61524c490ce1ec3c7e8a48702473044022074136f51a9f5dfc5b27e4e0b2a5b576f895f3e872f16bb74441b921ed670f7dd0220276d9f4fc40a1c0349008ae82135651077932010b5e9077bf62f8bc0b9142c33012102c7a49c3f29e63cf274819490e6e0bdc115756397ca60a86091f822286f5e9a8802473044022053aaaa8f122a8430342d274fdd57ea8dc746cce269e099d78ce2778656406c48022003e3788c5694ce17fcd842b6685f44afe6a7151dcfa214d4198d5503134d2d210121030541bdfff7b3e4deb1e0addf5715cbbb13407e7b06a9793a1be36233b7d49fdc000000006. 广播离线签名后的交易 9447820947c7fa1198a6342bbd06c22459a94e663a11aa2933d89f76e8597a80bitcoin-cli -regtest sendrawtransaction 020000000001018923686439bec2b6abadf6ec2afd62c34f7665d3580dd7314dc69f14b6c83b59010000001716001490c66a16ddd8405227e11efc87f7b93fce151373ffffffff01806de7290100000017a9149e82f61edb11d83f62a60368868a7a16bcf55a88870247304402203665a815716ed7c99a627acf896806101e851a9122a05a9dd337a7c70792800d022070b1a1c1947bbb81aff11e966392a4b05e3d4b2785a8170f70b4d0250f960e72012103d678d4c116ea3bff2b18760a45d86fd19692c213b217f88a6815006387290cfc00000000 以上的过程已经非常详尽了,现在对以上的过程进一步说明。 文章列出的步骤是满足以下场景的: 有两组地址,分别属于不同的私钥,从这两组地址中提取两个UTXO,输入到两个目标地址中,给每个目标地址49.9个比特币。这个过程称为第一次交易,在第一次交易广播之前,立刻发起第二笔交易。对第二笔交易签名完成之后,依次对这两个交易进行广播。 在步骤18之前,和前面文章中的构建复杂交易是完全一样的。为了简单降低文章的复杂度,并没有设置找零,如需设置找零,参考之前的文章,在创建交易之后设置找零地址和计算交易费即可。 再强调一遍,离线签署的重点是两个 在第一次交易未广播之前发起第二笔交易 在交易的最后,依次广播 额外提示,如果最后发现交易无法广播,请回头查阅签名问题。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"}]},{"title":"关于学习的两三事","slug":"关于学习的两三事","date":"2019-10-08T17:21:35.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/45941/","link":"","permalink":"https://imzy.vip/posts/45941/","excerpt":"昨日开周会leader在会后又一次强调了自己对于编程生涯的一些经验,也提出了一些对我们团队的一些告诫。以前参加过很多会议但是很少有人直接拿出自己的经验进行”传授“,所以作此文,对leader传授经验的经验进行复述。毕竟现在听到肺腑之言的机会也不是那么多。","text":"昨日开周会leader在会后又一次强调了自己对于编程生涯的一些经验,也提出了一些对我们团队的一些告诫。以前参加过很多会议但是很少有人直接拿出自己的经验进行”传授“,所以作此文,对leader传授经验的经验进行复述。毕竟现在听到肺腑之言的机会也不是那么多。 经验的核心可以分为几个基本问题 如何对待需要精通的知识 需要熟练的知识该如何处理 检验成长的方法 如何对待需要精通的知识这个问题其实还有个前置,我们需要去精通什么?最直接的就是我们需要精通使用的语言。leader指出,对于常用的语言一定要精通,我们对于语言本身的用法至少要阅读”三遍“。这三遍指的是学习语言本身的知识,并非是关于语言经验的总结。这三遍之中一定要包含一遍原版的文档。比如学习rust,那rust官方文档(英文版)是一定要严格过一遍的。要这么做的原因有下面这几条 阅读原版文档,避免了信息的损失和误传。毕竟翻译的再好,也有翻译者自己的理解在里面,会造成二手资料的问题。我们一定要有把握第一手资料的能力。 阅读原版文档会对语言本身的功能做一个全面的认识,一开始不阅读语言经验的知识也是这个原因。完整阅读文档之后,我们要用一门语言去做一件事能够清楚的认识到”能与不能的问题“。避免了因为认知不足造成的偏差。因为我们知识点不全,就完全不会在相关的方向思考,对于解决问题是个巨大缺陷。 原版资料可以提供对概念最精确的定义,一旦我们出现了问题,去搜索就可以非常精准的描述问题。对于编程(或者说学习)”精准的描述问题“,是一项非常重要的能力。如果我们能非常准确的描述问题标志着问题离解决已经迈进了一大步。 那其余的两遍做什么?其实还是阅读语言本身的知识。如果本身外文水准差,可以找一本自己母语的文档看,但这一遍必须是在阅读原版材料之后做。当然三是泛指,你可以阅读很多遍。对于精通,阅读再多遍也不过分。按照我的理解,知识需要覆盖型学习,因为”一遍就非常熟悉“是一种奢望。一遍就精通是个不切实际的目标,所以我们需要一遍一遍过来加深熟练度。我们对语言有一定的认识之后(懂了语法,写了一定量的代码),自然总结出语言的重点,这时候就可以阅读别人关于这门语言的经验。类似于《Effective Java》这类书籍就是在有一定经验之后总结出来的。我们也可以组织出自己的经验(这是必须的)。 需要熟练的知识该如何处理在需要专精的知识之外,是对于熟练知识的把握。直接点说,对工具的把握就是这类知识。比如写代码需要使用Clion。我们初始要做的很简单,就是直接用Clion写代码就可以了。用了一段时间之后发现网络不好,我们再去搜索”Clion如何设置代理“这类知识。或者用到另一个阶段了,我们需要debug,按照常理IDE肯定是有设置断点Debug的功能,我们再去搜索”Clion如何设置断点进行Debug“之类的知识。大部分情况下,我们并不需要像上面的精通类知识那样,先阅读一遍Clion功能文档再开始用(也不是不可以)。总结起来就是:面向需求学习,用到现学即可。 检验成长的方法无论是精通类知识还是熟练类知识,我们都有一个从无到有,从生疏到熟练的过程。整个过程必然伴随大量的思考。按照学习的理论,一个完整的学习过程包括输入,思考和输出三大过程。当然更好的学习模型还伴随着反馈和再学习的过程这里先不谈。学习过程没有输出是不完整的。如果我们学习了一门知识,一个语言,一种工具如果什么东西都没有产生,那这个过程就是失败的。学习是一个不断发现问题,解决问题的循环。我们大部分时候都可以记录这个过程,不犯同样的错误就是巨大的进步。当然大师们亦可以总结出《Effective Java》这样的经典之作。这也是我不断完善这个博客的理由。一个更简单的标准就是,你学习了一门语言有新人进来学习这门语言,”如果你拿不出有效的建议,说不出语言的重点,没找到语言的坑“那你的学习就是有问题的。当你内心产生,”这些问题都很简单没什么值得一提的“这个观念的话,那你学习问题就大了,建议再读一篇这篇文章。","categories":[{"name":"感悟","slug":"感悟","permalink":"https://imzy.vip/categories/%E6%84%9F%E6%82%9F/"}],"tags":[{"name":"学习","slug":"学习","permalink":"https://imzy.vip/tags/%E5%AD%A6%E4%B9%A0/"}]},{"title":"构建复杂交易","slug":"构建复杂交易","date":"2019-09-28T17:43:13.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/34596/","link":"","permalink":"https://imzy.vip/posts/34596/","excerpt":"本文在之前的基础上构建复杂交易。","text":"本文在之前的基础上构建复杂交易。 先对”复杂”进行定义 复杂指的是 我们使用两个交易输入,输出到两个地址上。且设定两个输入交易是输入不同的人的(输入属于不同的私钥)。 按照惯例列出主干过程 构建多输入,多输出的裸交易 依次进行签名 广播 因为之前的文章演示已经非常详细,故本文不在做额外展示,另外许多测试过程也不再列出,具体过程如下 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209// 复杂交易 所谓的复杂 定义为 2个输入 到两个输出符合官方例子中的情况//1. 启动私链bitcoind -printtoconsole -regtest//2. 查询链信息bitcoin-cli -regtest -getinfo//3. 为了符合复杂交易状况 生成第一个输入地址 2MzJ3PzMX6Q2b1yXLpJsQuBP5MmUbsmpQWebitcoin-cli -regtest getnewaddress//4. 导出搞地址私钥 备用 cQ9CcvcJziXzpZkwN5JRAptqPJobyaX63JByrEYPN5noBiW4kwn1bitcoin-cli -regtest dumpprivkey 2MzJ3PzMX6Q2b1yXLpJsQuBP5MmUbsmpQWe//5. 给这个地址挖矿 nblocks = 101 bitcoin-cli -regtest generatetoaddress 101 2MzJ3PzMX6Q2b1yXLpJsQuBP5MmUbsmpQWe//6. 获取当前账户下的信息 确认101个块 得到50个比特币bitcoin-cli -regtest getbalance//7. 准备第二个地址 2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9cbitcoin-cli -regtest getnewaddress//8. 导出私钥 cS6U3fHjJLb63WTVVRNEFw5DT2DPWefMGaUFwmBsRVm69vvub9iDbitcoin-cli -regtest dumpprivkey 2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9c//9. 挖矿 nblocks = 1bitcoin-cli -regtest generatetoaddress 1 2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9c//10. 再次查询账户信息bitcoin-cli -regtest getbalance//11. 生成两个地址作为输出 2N4EGkdkHKqhChzTG2bgM28xWcykZNkaUapbitcoin-cli -regtest getnewaddress//12. 继续 2MvgpC6bYH1GocTNTFwzNJtXX8t3FuN6gXFbitcoin-cli -regtest getnewaddress//13. 查询UTXObitcoin-cli -regtest listunspent//14. 确认数目不太够 多挖几次bitcoin-cli -regtest generatetoaddress 9 2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9cbitcoin-cli -regtest generatetoaddress 3 2MzJ3PzMX6Q2b1yXLpJsQuBP5MmUbsmpQWebitcoin-cli -regtest generatetoaddress 100 2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9c//15 挖了足够多的矿之后 (之所以挖这么多次,挖矿得100块个确认以后才可以用) 查询UTXObitcoin-cli -regtest listunspent注意以下的结果不是全部 随机截取了一部分 { "txid": "63e68a9f7a9d30022a402fe5594c5ab2adf4f6497ed10a248b58c39de0acbce6", "vout": 0, "address": "2MzJ3PzMX6Q2b1yXLpJsQuBP5MmUbsmpQWe", "label": "", "redeemScript": "00148812514082f6599305a1b5c23b9657f852d88099", "scriptPubKey": "a9144d514986fc491a05730271047dfa5526aff5d29687", "amount": 50.00000000, "confirmations": 217, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/13']03f606bed5b7003c34c5098725d3654be8df600a78e0940cf3d833d112066663dd))#lwl54ngn", "safe": true }, { "txid": "b1de08a9e8d803c5b34010773afb0cf607831dc36a7569192e5828f6959719eb", "vout": 0, "address": "2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9c", "label": "", "redeemScript": "0014e1183a028e4141aae89a68bcd1482fb9c692bf60", "scriptPubKey": "a914fba8b2c876c9e477be38d3f3c9fa81a8cca35bed87", "amount": 50.00000000, "confirmations": 116, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/14']02754e91c7f819cf0ffc1842ab00ec19cc5468229ad3a21c36906fa26836f0dfdc))#edq6mslq", "safe": true }, { "txid": "7024e9a21a2b55a93de2fa0d6828ac64243212140ea37055026b8cfeb3133deb", "vout": 0, "address": "2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9c", "label": "", "redeemScript": "0014e1183a028e4141aae89a68bcd1482fb9c692bf60", "scriptPubKey": "a914fba8b2c876c9e477be38d3f3c9fa81a8cca35bed87", "amount": 50.00000000, "confirmations": 118, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/14']02754e91c7f819cf0ffc1842ab00ec19cc5468229ad3a21c36906fa26836f0dfdc))#edq6mslq", "safe": true }, { "txid": "e7a5e1ea37512ec2609d18a9a4040e900e35656cbbf143bb352b42421d50c5f5", "vout": 0, "address": "2MzJ3PzMX6Q2b1yXLpJsQuBP5MmUbsmpQWe", "label": "", "redeemScript": "00148812514082f6599305a1b5c23b9657f852d88099", "scriptPubKey": "a9144d514986fc491a05730271047dfa5526aff5d29687", "amount": 50.00000000, "confirmations": 201, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/13']03f606bed5b7003c34c5098725d3654be8df600a78e0940cf3d833d112066663dd))#lwl54ngn", "safe": true }, { "txid": "d9be71cd09f9e9a47ef54b5b06db8c8c12aad28f0ef76f08b5a838d8a90a85fc", "vout": 0, "address": "2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9c", "label": "", "redeemScript": "0014e1183a028e4141aae89a68bcd1482fb9c692bf60", "scriptPubKey": "a914fba8b2c876c9e477be38d3f3c9fa81a8cca35bed87", "amount": 50.00000000, "confirmations": 120, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/14']02754e91c7f819cf0ffc1842ab00ec19cc5468229ad3a21c36906fa26836f0dfdc))#edq6mslq", "safe": true }, { "txid": "2dd092c66f124e326cc41de4c2c63aa8a4cc3f2b09d68d5eb4f15eae195834fd", "vout": 0, "address": "2MzJ3PzMX6Q2b1yXLpJsQuBP5MmUbsmpQWe", "label": "", "redeemScript": "00148812514082f6599305a1b5c23b9657f852d88099", "scriptPubKey": "a9144d514986fc491a05730271047dfa5526aff5d29687", "amount": 50.00000000, "confirmations": 224, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/13']03f606bed5b7003c34c5098725d3654be8df600a78e0940cf3d833d112066663dd))#lwl54ngn", "safe": true } 从中间提取两组分属于不同地址的交易作为复杂交易的输入 第一组: txid: 63e68a9f7a9d30022a402fe5594c5ab2adf4f6497ed10a248b58c39de0acbce6 vout: 0 address: 2MzJ3PzMX6Q2b1yXLpJsQuBP5MmUbsmpQWe privkey: cQ9CcvcJziXzpZkwN5JRAptqPJobyaX63JByrEYPN5noBiW4kwn1 第二组: txid: b1de08a9e8d803c5b34010773afb0cf607831dc36a7569192e5828f6959719eb vout: 0 address: 2NGBsgoTEPFHF5qu83o91ZXu8JE9zT2HL9c privkey: cS6U3fHjJLb63WTVVRNEFw5DT2DPWefMGaUFwmBsRVm69vvub9iD 目标地址 2N4EGkdkHKqhChzTG2bgM28xWcykZNkaUap 2MvgpC6bYH1GocTNTFwzNJtXX8t3FuN6gXF16. 在这个基础上创建裸交易 给两个地址分别发送49.9个比特币bitcoin-cli -regtest createrawtransaction ''' [ { "txid": "63e68a9f7a9d30022a402fe5594c5ab2adf4f6497ed10a248b58c39de0acbce6", "vout": 0 }, { "txid": "b1de08a9e8d803c5b34010773afb0cf607831dc36a7569192e5828f6959719eb", "vout": 0 } ] ''' ''' { "2N4EGkdkHKqhChzTG2bgM28xWcykZNkaUap": 49.9, "2MvgpC6bYH1GocTNTFwzNJtXX8t3FuN6gXF": 49.9 }'''结果0200000002e6bcace09dc3588b240ad17e49f6f4adb25a4c59e52f402a02309d7a9f8ae6630000000000ffffffffeb199795f628582e1969756ac31d8307f60cfb3a771040b3c503d8e8a908deb10000000000ffffffff02805b6d290100000017a914787b46861a008ef11cf4168db5e2c787afa0b25e87805b6d290100000017a91425bf54042e8fdc3f50d00ada9fdae02fe4313c91870000000017. 现在一次进行签名 这里使用私钥进行签名bitcoin-cli -regtest signrawtransactionwithkey "0200000002e6bcace09dc3588b240ad17e49f6f4adb25a4c59e52f402a02309d7a9f8ae6630000000000ffffffffeb199795f628582e1969756ac31d8307f60cfb3a771040b3c503d8e8a908deb10000000000ffffffff02805b6d290100000017a914787b46861a008ef11cf4168db5e2c787afa0b25e87805b6d290100000017a91425bf54042e8fdc3f50d00ada9fdae02fe4313c918700000000" "[\\"cQ9CcvcJziXzpZkwN5JRAptqPJobyaX63JByrEYPN5noBiW4kwn1\\"]"{ "hex": "02000000000102e6bcace09dc3588b240ad17e49f6f4adb25a4c59e52f402a02309d7a9f8ae66300000000171600148812514082f6599305a1b5c23b9657f852d88099ffffffffeb199795f628582e1969756ac31d8307f60cfb3a771040b3c503d8e8a908deb10000000000ffffffff02805b6d290100000017a914787b46861a008ef11cf4168db5e2c787afa0b25e87805b6d290100000017a91425bf54042e8fdc3f50d00ada9fdae02fe4313c91870247304402200323249f760bfe0683673b1af15c893bb695ae154f961c1eb133eacaed89c604022006ec0658177cf101264cd50b23f34a945ecd5359c0f15039962d8ec859a5c497012103f606bed5b7003c34c5098725d3654be8df600a78e0940cf3d833d112066663dd0000000000", "complete": false, "errors": [ { "txid": "b1de08a9e8d803c5b34010773afb0cf607831dc36a7569192e5828f6959719eb", "vout": 0, "witness": [ ], "scriptSig": "", "sequence": 4294967295, "error": "Unable to sign input, invalid stack size (possibly missing key)" } ]}18. 进行第二次签名 注意这次需要上次签名之后的hex 也就是说需要签名之后再签 签名是逐渐变长的{ "hex": "02000000000102e6bcace09dc3588b240ad17e49f6f4adb25a4c59e52f402a02309d7a9f8ae66300000000171600148812514082f6599305a1b5c23b9657f852d88099ffffffffeb199795f628582e1969756ac31d8307f60cfb3a771040b3c503d8e8a908deb10000000017160014e1183a028e4141aae89a68bcd1482fb9c692bf60ffffffff02805b6d290100000017a914787b46861a008ef11cf4168db5e2c787afa0b25e87805b6d290100000017a91425bf54042e8fdc3f50d00ada9fdae02fe4313c91870247304402200323249f760bfe0683673b1af15c893bb695ae154f961c1eb133eacaed89c604022006ec0658177cf101264cd50b23f34a945ecd5359c0f15039962d8ec859a5c497012103f606bed5b7003c34c5098725d3654be8df600a78e0940cf3d833d112066663dd02473044022070bf83bb965f10cdd949f696b4268bdaac9a4349250ec9acba28d8e113e5152702204eef794121db0701aeafbe13e0f6cc0db270ed6840b8155071af46b8f644f886012102754e91c7f819cf0ffc1842ab00ec19cc5468229ad3a21c36906fa26836f0dfdc00000000", "complete": true}19. 广播即可 不再展示 按照以上过程即可完成。 这个过程的重点是依次签名,第一次签名之后,在第一次签名的基础上再次签名。签上加签。以此类推,如果需要 n 个输入就连续进行 n 次签名。 还有需要注意的点 因为是两个输入分别属于不同的人(不同的秘钥),所以我们使用 signrawtransactionwithkey 方法,而不是之前例子中使用钱包的签名方法。 这个过程中也使用了 createrawtransaction ,未设定找零地址,所以输入和输出的差值还是会作为交易费用贡献给矿工,请谨慎操作 如果要使用带有找零的交易,参考上一篇文章需要使用 fundrawtrastion 指定找零地址和计算交易费,如在本文的流程中,可以在步骤16之后使用 fundrawtrastion 指定找零地址。然后再签名。 直接操作私钥是一种非常危险的行为,请参考者清楚自己在做什么,以免造成不必要的损失 最后再留一个引子,假如第19步不进行广播,我们可以在第19步的基础上进行离线签名。换句话说,这个交易不进行广播,那他就不存在交易节点上或者内存池中,是一个不可信交易(unconfirmed transactions)。关于离线签名( Offline Signing)是什么,有什么作用,如何使用会在后续的文章中列出。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"},{"name":"复杂交易","slug":"复杂交易","permalink":"https://imzy.vip/tags/%E5%A4%8D%E6%9D%82%E4%BA%A4%E6%98%93/"}]},{"title":"创建具有找零地址的交易","slug":"创建具有找零地址的交易","date":"2019-09-25T17:05:54.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/27102/","link":"","permalink":"https://imzy.vip/posts/27102/","excerpt":"之前的文章讲了构建裸交易的过程,但是上文直接构建裸交易是没有创建找零地址的,所以输入和输出的差值都会成为矿工的交易费。极容易造成高额的交易费,所以这篇文章在之前的基础上使用 fundrawtransaction 来创建具有找零地址的交易。","text":"之前的文章讲了构建裸交易的过程,但是上文直接构建裸交易是没有创建找零地址的,所以输入和输出的差值都会成为矿工的交易费。极容易造成高额的交易费,所以这篇文章在之前的基础上使用 fundrawtransaction 来创建具有找零地址的交易。 先列出主干 创建一笔没有输入的交易 添加充足的未签名的的交易来满足交易需求 签名 广播 具体过程 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202// 启动私链1. bitcoind -printtoconsole -regtest // 其余参数全部参照原来的设计,其余几个项目都去 .bitcoin/bitcoin.conf 下去掉////先测试私链起来了没有2. bitcoin-cli -regtest -getinfo//在查询一下3. bitcoin-cli -regtest getbestblockhash//查询当前用户信息 不出所料的话都应该没有币的4. bitcoin-cli -regtest getbalance//生成一个地址作为初始地址 2N9nBBNFP4BFFZWzvQTwQKF96RxidHx4p8K5. bitcoin-cli -regtest getnewaddress//验证一下这个地址6. bitcoin-cli -regtest validateaddress 2N9nBBNFP4BFFZWzvQTwQKF96RxidHx4p8K// 输出{ "isvalid": true, "address": "2N9nBBNFP4BFFZWzvQTwQKF96RxidHx4p8K", "scriptPubKey": "a914b55c9006466ed8bad87d9f7af0aed405764eb0a887", "isscript": true, "iswitness": false}//尝试导出这个私钥 cQUSPugSndqrg9UkVKvgeowJpptyyxHFEE5TcstjJnw8NzeUKPCg7. bitcoin-cli -regtest dumpprivkey 2N9nBBNFP4BFFZWzvQTwQKF96RxidHx4p8K//开始挖矿 注意这里的 api 发生了变化 以前是直接generate 现在推荐下面的方法 直接100个区块到之前生成的地址上8. bitcoin-cli -regtest generatetoaddress 101 2N9nBBNFP4BFFZWzvQTwQKF96RxidHx4p8K//接下来 获取当前账户下的信息 确认一百个块 得到50个比特币9. bitcoin-cli -regtest getbalance//再生成第二个地址 用于接受转账 2NByxgfmQxVuQXB16GY1J2ru6RGfNEdgPk310. bitcoin-cli -regtest getnewaddress//先创建一笔没有输入的交易 转账给我们的目标地址35个比特币 02000000000100c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea870000000011. bitcoin-cli -regtest createrawtransaction '[]' '{"2NByxgfmQxVuQXB16GY1J2ru6RGfNEdgPk3":35}'//接下来decode 12. bitcoin-cli -regtest decoderawtransaction 02000000000100c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8700000000输出{ "txid": "776b54a0b65d3af5fd13ab4d947ab72e563ba7a2aa43cd37ebdd820dc50ec9ca", "hash": "776b54a0b65d3af5fd13ab4d947ab72e563ba7a2aa43cd37ebdd820dc50ec9ca", "version": 2, "size": 42, "vsize": 42, "weight": 168, "locktime": 0, "vin": [ ], "vout": [ { "value": 35.00000000, "n": 0, "scriptPubKey": { "asm": "OP_HASH160 cd876025d3d13aa7ed999128d40100a9f26a65ea OP_EQUAL", "hex": "a914cd876025d3d13aa7ed999128d40100a9f26a65ea87", "reqSigs": 1, "type": "scripthash", "addresses": [ "2NByxgfmQxVuQXB16GY1J2ru6RGfNEdgPk3" ] } } ]}//使用 fundrawtransaction 计算出交易费 输入为刚才得到的 raw transaction 02000000000100c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8700000000 13. bitcoin-cli -regtest fundrawtransaction 02000000000100c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8700000000{ "hex": "02000000019e667f70450d80e036c5763b4ece68479a047a67efcf483f8bf2e56c50ca5f140000000000feffffff02082268590000000017a914ebec149b57c6c84ab9a5302dae8150369d9c51fd8700c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8700000000", "fee": 0.00003320, "changepos": 0}// decoderawtransaction 14. bitcoin-cli -regtest decoderawtransaction 02000000019e667f70450d80e036c5763b4ece68479a047a67efcf483f8bf2e56c50ca5f140000000000feffffff02082268590000000017a914ebec149b57c6c84ab9a5302dae8150369d9c51fd8700c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8700000000{ "txid": "704bd8b218ab5dc934a6388f139659f0626a93af1d77814d34a8123590641500", "hash": "704bd8b218ab5dc934a6388f139659f0626a93af1d77814d34a8123590641500", "version": 2, "size": 115, "vsize": 115, "weight": 460, "locktime": 0, "vin": [ { "txid": "145fca506ce5f28b3f48cfef677a049a4768ce4e3b76c536e0800d45707f669e", "vout": 0, "scriptSig": { "asm": "", "hex": "" }, "sequence": 4294967294 } ], "vout": [ { "value": 14.99996680, "n": 0, "scriptPubKey": { "asm": "OP_HASH160 ebec149b57c6c84ab9a5302dae8150369d9c51fd OP_EQUAL", "hex": "a914ebec149b57c6c84ab9a5302dae8150369d9c51fd87", "reqSigs": 1, "type": "scripthash", "addresses": [ "2NEkfbXNcp3iQtQjh4RemfTGeGQ6S8uX5kC" ] } }, { "value": 35.00000000, "n": 1, "scriptPubKey": { "asm": "OP_HASH160 cd876025d3d13aa7ed999128d40100a9f26a65ea OP_EQUAL", "hex": "a914cd876025d3d13aa7ed999128d40100a9f26a65ea87", "reqSigs": 1, "type": "scripthash", "addresses": [ "2NByxgfmQxVuQXB16GY1J2ru6RGfNEdgPk3" ] } } ]}// 利用钱包的功能对这次交易进行签名15. bitcoin-cli -regtest signrawtransactionwithwallet 02000000019e667f70450d80e036c5763b4ece68479a047a67efcf483f8bf2e56c50ca5f140000000000feffffff02082268590000000017a914ebec149b57c6c84ab9a5302dae8150369d9c51fd8700c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8700000000{ "hex": "020000000001019e667f70450d80e036c5763b4ece68479a047a67efcf483f8bf2e56c50ca5f1400000000171600145ffd0df9539eaf3036a524bf7c41ed5864cc2334feffffff02082268590000000017a914ebec149b57c6c84ab9a5302dae8150369d9c51fd8700c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8702473044022054074aff080169dc692cb1952eb97205f7c1b7b4a2f14903ee270271fcc9b19102205fdafcafbbb466350e48d891498c1244238866bf17ac11515a1cc4b796fbfdaa0121034824cd575c0c7474fbb49c255ad81892f673f68c2025f5bacfc60bbec910b1ac00000000", "complete": true}// 广播 668911f8a41113ffdbf65fd326e20c107dfd62cf3cc86959444f55be94ab78d716. bitcoin-cli -regtest sendrawtransaction 020000000001019e667f70450d80e036c5763b4ece68479a047a67efcf483f8bf2e56c50ca5f1400000000171600145ffd0df9539eaf3036a524bf7c41ed5864cc2334feffffff02082268590000000017a914ebec149b57c6c84ab9a5302dae8150369d9c51fd8700c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8702473044022054074aff080169dc692cb1952eb97205f7c1b7b4a2f14903ee270271fcc9b19102205fdafcafbbb466350e48d891498c1244238866bf17ac11515a1cc4b796fbfdaa0121034824cd575c0c7474fbb49c255ad81892f673f68c2025f5bacfc60bbec910b1ac00000000//可以查看一下 17. bitcoin-cli -regtest getrawtransaction 668911f8a41113ffdbf65fd326e20c107dfd62cf3cc86959444f55be94ab78d7 1输出{ "txid": "668911f8a41113ffdbf65fd326e20c107dfd62cf3cc86959444f55be94ab78d7", "hash": "53171009d8f8984d72276e6f4f19bad12476d272ba6728e8323a2bb3756c77ac", "version": 2, "size": 247, "vsize": 166, "weight": 661, "locktime": 0, "vin": [ { "txid": "145fca506ce5f28b3f48cfef677a049a4768ce4e3b76c536e0800d45707f669e", "vout": 0, "scriptSig": { "asm": "00145ffd0df9539eaf3036a524bf7c41ed5864cc2334", "hex": "1600145ffd0df9539eaf3036a524bf7c41ed5864cc2334" }, "txinwitness": [ "3044022054074aff080169dc692cb1952eb97205f7c1b7b4a2f14903ee270271fcc9b19102205fdafcafbbb466350e48d891498c1244238866bf17ac11515a1cc4b796fbfdaa01", "034824cd575c0c7474fbb49c255ad81892f673f68c2025f5bacfc60bbec910b1ac" ], "sequence": 4294967294 } ], "vout": [ { "value": 14.99996680, "n": 0, "scriptPubKey": { "asm": "OP_HASH160 ebec149b57c6c84ab9a5302dae8150369d9c51fd OP_EQUAL", "hex": "a914ebec149b57c6c84ab9a5302dae8150369d9c51fd87", "reqSigs": 1, "type": "scripthash", "addresses": [ "2NEkfbXNcp3iQtQjh4RemfTGeGQ6S8uX5kC" ] } }, { "value": 35.00000000, "n": 1, "scriptPubKey": { "asm": "OP_HASH160 cd876025d3d13aa7ed999128d40100a9f26a65ea OP_EQUAL", "hex": "a914cd876025d3d13aa7ed999128d40100a9f26a65ea87", "reqSigs": 1, "type": "scripthash", "addresses": [ "2NByxgfmQxVuQXB16GY1J2ru6RGfNEdgPk3" ] } } ], "hex": "020000000001019e667f70450d80e036c5763b4ece68479a047a67efcf483f8bf2e56c50ca5f1400000000171600145ffd0df9539eaf3036a524bf7c41ed5864cc2334feffffff02082268590000000017a914ebec149b57c6c84ab9a5302dae8150369d9c51fd8700c39dd00000000017a914cd876025d3d13aa7ed999128d40100a9f26a65ea8702473044022054074aff080169dc692cb1952eb97205f7c1b7b4a2f14903ee270271fcc9b19102205fdafcafbbb466350e48d891498c1244238866bf17ac11515a1cc4b796fbfdaa0121034824cd575c0c7474fbb49c255ad81892f673f68c2025f5bacfc60bbec910b1ac00000000"}","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"},{"name":"fundrawtransaction","slug":"fundrawtransaction","permalink":"https://imzy.vip/tags/fundrawtransaction/"}]},{"title":"使用比特币节点进行交易","slug":"使用比特币节点进行交易","date":"2019-09-21T14:18:46.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/60903/","link":"","permalink":"https://imzy.vip/posts/60903/","excerpt":"使用比特币节点进行交易如果读过之前的完整的一系列文章,应该已经经历了从节点源代码编译,运行,和配置 rpc 的完全过程,本文在之前工作的过程上进行总结,使用比特币节点发起一笔交易。","text":"使用比特币节点进行交易如果读过之前的完整的一系列文章,应该已经经历了从节点源代码编译,运行,和配置 rpc 的完全过程,本文在之前工作的过程上进行总结,使用比特币节点发起一笔交易。 注意:读了之前的文章也应该清楚一点,比特币节点代码和 rpc api 更新较快。本文依赖比特币 0. 18 版本,系统环境乌班图。 比特币发起交易步骤首先列出比特币交易的最主干的过程如下 提取UTXO 发起交易 对交易进行签名 广播交易 提取UXTO交易的最重要的部分就是一开始的提取UXTO的过程。根据官方的 RPC Api 。我们可以使用 listunspent 的api 来获取UTXO的列表。 这是交易过程中第一个会出现问题的地方,如果我们直接使用这个 api 会发现一个问题,我们可能会发现完全提取不倒信息。 这是因为我们想要发起交易,很大程度上依赖的是比特币节点的钱包的功能。(关于比特币全节点的概念,请参考《精通比特币》一书)。这个比特币地址如果和本节点上的钱包是没有关联的话,我们是无法得到UTXO的。 接下来介绍什么叫做关联:根据比特币早期的文档(文档版本比较老,可以去查阅早期的wiki文档,不再列出)。比特币早期中有账号的概念。也就是说,比特币钱包都是依赖账号来管理的 Account。比特币的地址都是存在某个账号下的。之所以直接查询对应的比特币地址没有信息。是因为我们的地址,不是比特币钱包的账号下面的,所以我们直接去过滤相应地址下的UTXO是没有信息的。 当然后期的版本比特币不再强调账号的概念,换成了如下体系。 比特币钱包使用 name 属性来管理,钱包内部使用 label 对地址进行管理(图稍后补上) 在体系途中 xxxxx yyyyy zzzzz代表地址,Alvin Mike 代表钱包的 name。我们直接查询的 zzzzz 属于和钱包没有关系的地址,属于 没有关联。 如何关联那么如何关联呢,其实很简单,我们只需要把地址对应的私钥导入钱包即可。我们可以使用比特币 RPC Api 中的 importprivkey 来导入私钥。这样这个地址就纳入了钱包中,属于地址和钱包进行了关联。我们可以参考文档 importprivkey 请仔细参阅文档,注意参数 rescan。导入私钥请开启 rescan,这样的话节点会对这个地址进行扫描,对该地址对应的 UTXO 进行索引。这样就可以查询 UTXO 了。当然他需要一个较长时间构建索引,在运行期间你可能无法得到正确的结果,你只需要等待 rescan 完成即可。 不导入私钥可以吗不导入私钥是可以的,我们参阅文档可以得到以下两种方法,都可以在不导入私钥的情况下查询UXTO 在启动节点的时候加入 txindex = 1; 这个参数可以帮助你建立所有的交易索引,这样你就可以在节点上查询所有地址的交易信息了。大胆猜测,比特币浏览器之类的节点需要这种配置 可以只导入地址,假定只导入地址的话使用 importprivkey ,没有私钥,无法签名。作为只读钱包还是可以的。具体请参阅文档。 以上两种不是很方便,就不再详细介绍了。 再强调一下 网上搜集很多教程中,都会出现从起一个节点开始,然后使用 getnewaddress 这个 Api 。这种操作其实就是就规避了关联 这个问题,因为直接用钱包的功能生成的地址就是挂在钱包下面的,所以我们可以很容易的查询这种地址下面的 UTXO。 发起交易确切的说,我们需要的是发起裸交易,(也有 sendtoadddress 方法,这个不属于本文的讨论范围)。这个就比较直接,这里依然可以从前文提到的链接中方便的找到说明。 发起裸交易的核心就是从之前的某交易中,支付一定数量的比特币到目标地址。至少要满足的条件是:你发的比特币数量要低于交易中的比特币总量。毕竟钱不够如何支付呢。 注意:我们使用的是 createrawtransaction 。这种交易方式不会生成找零地址。则输入和输出之差会成为交易费用 fee 将会成为矿工的奖励。这种交易会造成超高费用交易,同时会影响最后一步的交易广播。关于对广播的影响,我们稍后再谈。如果我们需要创建找零地址,请使用 fundcreaterawtransaction。(后面可能会出现专门的一篇介绍这个) 总之这一步之后 我们会得到一个未经过签名的 rawhex 交易签名得到一个裸交易的 hexstring 之后,需要我们对交易进行签名,在当前 0.18 版本下我们有两条签名路径 使用钱包的签名功能 [https://bitcoincore.org/en/doc/0.18.0/rpc/wallet/signrawtransactionwithwallet/] 使用钱包的签名功能需要我们事先导入私钥,也就是说地址对应的私钥和钱包是有关联的 使用私钥进行签名 [https://bitcoincore.org/en/doc/0.18.0/rpc/rawtransactions/signrawtransactionwithkey/] 使用私钥进行签名,需要我们有发起地址的所有权,也就是有私钥 无论使用哪种签名,都可以得到签名之后的 hexstring 。 这个也是交易中最关键的步骤步骤,如果我们签名正常结束,我们会得到签名的结果 “complete”: true 同时得到一个更长的经过签名的 hexstring 需要注意的是:出现complete: true 这种结果并不代表签名就是万无一失的,这种签名可能会有别的错误,我们只是提供了合适的参数进行签名而已。如果这里的签名不正常,这次交易是无法广播的。 如果需要耍个小聪明,稍微调整参数,你会发现得到的签名后 hex 是有细微差别的(这个很容易理解,因为这一串字符串本身就是拼接而来的) 所以无法广播,请优先考虑签名错误。请仔细对照文档中的PrevTX参数。 广播交易如果我们前面三个步骤正确,到这一步直接广播交易即可 我们使用 sendrawtransaction 方法即可,参数就是上一步中得到的 $SIGNED_RAW_TRANSACTION_STRING 这里提到的一个超高交易费的问题,如果我们使用之前的 createrawtaranstion 是没有设置找零地址的,所以这里直接广播是不行的。需要加上参数 allowhignfee 。参考文档。 在允许高额交易费用这个错误之外,我们还可能遇到其他错误,比如我在 Testnet 环境下遇到了 Missing Input 的错误。或者其他类型的错误。排错原则为:优先检查交易签名 之后等待挖矿即可。后文将列出我直接使用 bitcoin-cli 进行交易的过程,为了保持文档的完整性,中间出错的步骤并没有删除,是完整保留的。下文的命令行操作过程是百分百完成的 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778启动私链 发起一个交易1. bitcoind -printtoconsole -regtest // 其余参数全部参照原来的设计,其余几个项目都去 .bitcoin/bitcoin.conf 下去掉//先测试私链起来了没有2. bitcoin-cli -regtest -getinfo//在查询一下3. bitcoin-cli -regtest getbestblockhash//查询当前用户信息 不出所料的话都应该没有币的4. bitcoin-cli -regtest getbalance//现在生成一个地址 得到一个地址 2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e 这个地址作为初始的地址5. bitcoin-cli -regtest getnewaddress//验证一下这个地址6. bitcoin-cli -regtest validateaddress 2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e//尝试导出这个私钥 cS3c2UNjTjhREK59T7YK93NrvF9vUTXdP9d6vgR9bqYVJTizQtuj7. bitcoin-cli -regtest dumpprivkey 2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e//开始挖矿 注意这里的 api 发生了变化 以前是直接generate 现在推荐下面的方法 直接100个区块到之前生成的地址上8. bitcoin-cli -regtest generatetoaddress 101 2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e//接下来 获取当前账户下的信息 确认一百个块 得到50个比特币9. bitcoin-cli -regtest getbalance//再生成第二个地址 用于接受转账 2N9KXa3WdbbGRYM4XgNWqnQemDRSj5fqgDh10. bitcoin-cli -regtest getnewaddress//接下来 提取utxo11. bitcoin-cli -regtest listunspent [ { "txid": "bea69fa6b4d2395f340f8b57ab5c6b0afd3de451b63cf335848e3f11885e89b4", "vout": 0, "address": "2N4cpCv7VgohRaQAxWuknR5dg97ryBFR91e", "label": "", "redeemScript": "0014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292", "scriptPubKey": "a9147cbeaeb1f3919c7e0963c635287ce3385dfa91dd87", "amount": 50.00000000, "confirmations": 101, "spendable": true, "solvable": true, "desc": "sh(wpkh([bec83093/0'/0'/0']0295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c3012180))#pv6ux7wl", "safe": true }]//创建一个裸交易 给新生成的地址转20个比特币 得到一个交易id 0200000001b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000000ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef870000000012. bitcoin-cli -regtest createrawtransaction '[{"txid" :"bea69fa6b4d2395f340f8b57ab5c6b0afd3de451b63cf335848e3f11885e89b4","vout" : 0}]' '{"2N9KXa3WdbbGRYM4XgNWqnQemDRSj5fqgDh": 20}'//使用私钥进行签名13. bitcoin-cli -regtest signrawtransactionwithwallet 0200000001b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000000ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8700000000 '[{"txid":"bea69fa6b4d2395f340f8b57ab5c6b0afd3de451b63cf335848e3f11885e89b4", "vout":0, "scriptPubKey":"a9147cbeaeb1f3919c7e0963c635287ce3385dfa91dd87","amount":20, "redeemScript":"0014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292"}]'得到{ "hex": "02000000000101b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000017160014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8702473044022013597740ee733abe79849008462e3e87cc96c6e7e195fbcb74a7519c9df37e04022039ee7076ad688fa68f4a4d4497b5e0b2504682137e0359c100c49310ebdaef9401210295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c301218000000000", "complete": true}// 广播交易 出错14. bitcoin-cli -regtest sendrawtransaction 02000000000101b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000017160014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8702473044022013597740ee733abe79849008462e3e87cc96c6e7e195fbcb74a7519c9df37e04022039ee7076ad688fa68f4a4d4497b5e0b2504682137e0359c100c49310ebdaef9401210295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c301218000000000//只使用;裸交易id进行签名 15. bitcoin-cli -regtest signrawtransactionwithwallet "0200000001b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000000ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef8700000000"{ "hex": "02000000000101b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000017160014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef870247304402205388b5674d8874a8fc9ae7acbd39f14a0658163c22684dcd8bc32073ec295d1702206cbda91f9bbfc5479e380077ff2ba5e480a402f883fc638b8c82ad71e01977bc01210295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c301218000000000", "complete": true}// 广播16. bitcoin-cli -regtest sendrawtransaction "02000000000101b4895e88113f8e8435f33cb651e43dfd0a6b5cab578b0f345f39d2b4a69fa6be0000000017160014cecad7bc6e1e2a2aa842474e6f26e8bf45f02292ffffffff01009435770000000017a914b0525a68d151f78a49bfaa86b6456b856e3a64ef870247304402205388b5674d8874a8fc9ae7acbd39f14a0658163c22684dcd8bc32073ec295d1702206cbda91f9bbfc5479e380077ff2ba5e480a402f883fc638b8c82ad71e01977bc01210295b3ceb919a0364a790361d8557702af364343d418b2e32fb95e1627c301218000000000" true得到 3b3c8646fb03764aae2b2d9c8ef81704576f94c356e2d38f76bcd98ea4d9a5ae 已完成交易 等待挖矿即可 额外提醒在完成以上操作后,就实现了利用比特币节点进行交易,此外再提供两点温馨提示 如果使用私链,使用命令行启动和使用bitcoin.conf启动,这两个配置一定不能冲突,否则比特币节点是无法启动的。(当然别的方式这两个配置也不能冲突,算是比特币一个比较人性化的地方?) Bitcoin-cli 操作本质上是使用 json-rpc 使用 curl 一样可以达到这个效果。当然也不是为了使用代码进行交互。这里使用的认证方式是 Basic。如果需要写代码,注意这一点。之后可能会列出我自己 封装的 bitcoin-json rpc 代码。 实在想测试,建议使用私链。环境比较纯净。你不会遇到:比特币过少,网络差等问题。最主要的是你可以自己挖矿,适合急性子。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"},{"name":"总结","slug":"总结","permalink":"https://imzy.vip/tags/%E6%80%BB%E7%BB%93/"}]},{"title":"比特币节点远程访问","slug":"比特币节点远程访问","date":"2019-09-03T15:05:11.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/42988/","link":"","permalink":"https://imzy.vip/posts/42988/","excerpt":"比特币节点远程访问在上一篇文章中详细描述了比特币节点的配置,其中诸多参数的意义可以在上文中找到解释,也可以使用 -help 或者 -?来查看说明。本文讲是配置一台运行比特币的服务器(我的环境是乌班图),远程访问比特币核心。","text":"比特币节点远程访问在上一篇文章中详细描述了比特币节点的配置,其中诸多参数的意义可以在上文中找到解释,也可以使用 -help 或者 -?来查看说明。本文讲是配置一台运行比特币的服务器(我的环境是乌班图),远程访问比特币核心。 通过比特币核心提供的RPC服务来和比特币核心进行交互。在网上用配置比特币远程服务等关键字搜索得到的信息好多都是过时的,因为比特币版本升级迭代的比较快。请注意本文配置的时间节点是 2019-9-1,后续参考本文请注意时间节点。 在默认的状态下,bitcoind(以后统称为bitcoind),监听的是本地回环地址 127.0.0.1 。默认监听的正式地址的端口是 8332 123$ netstat -alpn | grep 8332tcp 0 0 127.0.0.1:8332 0.0.0.0:* LISTEN 18787/bitcoindtcp6 0 0 ::1:8332 :::* LISTEN 18787/bitcoind 在本地节点使用 bitcoin-cli 或者本地(same machine)访问节点是没问题的,使用 localhost 即可(可以不要用户名和密码)。但是无法远程访问。 在linux系统下,配置放在 ~/.bitcoin/bitcoin.conf 中,如果你要远程访问,配置参考如下 1234567# 1代表开启rpc服务 0代表关闭server=1rpcbind=10.0.1.5rpcallowip=0.0.0.0/0rpcport=8332rpcuser=bitcoinrpcpassword=password 需要注意的是 当开启 rpc 服务时刻,直接访问运行 bitcoind 主机的 ip 会看到 JSONRPC server handles only POST 以上这句话代表 rpc 服务是开启的。 如果你用以上的配置去启动 bitcoind ,就会把 rpc 服务绑定在 ip “10.0.1.5” 上,同时使用了 port 8332。当然,为了授权 user 和 password 也是必须的。 比特币 rpc 服务使用的是白名单模式,你要指定允许访问的 ip 必须在配置 rpcallowip 指定,不然的话 rpcbind 是无法生效的。也就是说 rpcallowip 和 rpcbind 需要同时设定,你可以看到这句话 -rpcbind=[:port]Bind to given address to listen for JSON-RPC connections. Do not exposethe RPC server to untrusted networks such as the public internet!This option is ignored unless -rpcallowip is also passed. Port isoptional and overrides -rpcport. Use [host]:port notation forIPv6. This option can be specified multiple times (default:127.0.0.1 and ::1 i.e., localhost) 想允许更多的 ip 访问 bitcoind 可以参考我的配置。网上很多文章中提到的 使用 * 做通配符,是过时的。这么做会导致 bitcoind 无法正常启动 。具体可以参考比特币官网文档。 注意:我这种配置是极度不安全的,强烈不建议这么做,为了安全起见请合理使用白名单。 然后可以找一台白名单中的机器调用服务端的 rpc 接口 1curl --user bitcoin --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getnetworkinfo", "params": [] }' -H 'content-type: text/plain;' http://10.0.1.5:8332/ 能正确获取 json 就好了。 如果不会 curl 请参考这一篇 https://man.linuxde.net/curl 参考文章 https://ma.ttias.be/enable-the-rpc-json-api-with-password-authentication-in-bitcoin-core/ 关于 rpcbind 的说明请参考(我 checkout 版本为0.18) https://bitcoin.org/en/release/v0.18.0#configuration-option-changes 小结网络上资料都有时效性,目前很多资料都没提到 rpcbind ,请多阅读比特币官方说明和检索外文资料。这类资料价值相对更高,利于快速解决问题。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"RPC","slug":"RPC","permalink":"https://imzy.vip/tags/RPC/"}]},{"title":"比特币客户端的设置","slug":"比特币客户端的设置","date":"2019-08-31T15:13:24.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/55622/","link":"","permalink":"https://imzy.vip/posts/55622/","excerpt":"比特币客户端的设置关于如何从源码编译出一个比特币程序网上已经有很多文章了,而且写的非常详细,按照上面的步骤一步步的安装依赖库就可以编译出一个完整的客户端。下面列出搜集到比特币客户端的设置,作为资料备查。","text":"比特币客户端的设置关于如何从源码编译出一个比特币程序网上已经有很多文章了,而且写的非常详细,按照上面的步骤一步步的安装依赖库就可以编译出一个完整的客户端。下面列出搜集到比特币客户端的设置,作为资料备查。 我安装的环境是乌班图系统 关于比特币编译过程可以参考这一篇 Bitcoin 比特币官方客户端有两个版本:一个是图形界面的版本,通常被称为bitcoin-qt,以及一个简洁的版本(称为bitcoind)。它们相互间是兼容的,有着同样的命令行参数,读取相同的配置文件,也读写相同的数据文件。你可以在一台电脑中运行Bitcoin 客户端或是bitcoind 客户端的其中一个(如果不小心尝试同时运行另外一个客户端,它会提示你已经有一个客户端在运行并且自动退出)。当然简易的配置直接参考《精通比特币》一书的配置,根据自己安装环境选择两类配置。 命令行参数使用 -? 或–help 参数运行Bitcoin 或bitcoind,它会提示常用的命令行参数并退出。用法: 1234bitcoind [选项]bitcoind [选项] <命令> [参数] 将命令发送到 -server 或 bitcoindbitcoind [选项] help 列出命令bitcoind [选项] help <命令> 获取该命令的帮助 选项: 123456789101112131415161718192021222324252627282930313233343536373839404142-conf=<文件名> 指定配置文件(默认:bitcoin.conf)-pid=<文件名> 指定 pid (进程 ID)文件(默认:bitcoind.pid)-gen 生成比特币-gen=0 不生成比特币-min 启动时最小化-splash 启动时显示启动屏幕(默认:1)-datadir=<目录名> 指定数据目录-dbcache=<n> 设置数据库缓存大小,单位为兆字节(MB)(默认:25)-dblogsize=<n> 设置数据库磁盘日志大小,单位为兆字节(MB)(默认:100)-timeout=<n> 设置连接超时,单位为毫秒-proxy=<ip:端口> 通过 Socks4 代理链接-dns addnode 允许查询 DNS 并连接-port=<端口> 监听 <端口> 上的连接(默认:8333,测试网络 testnet:18333)-maxconnections=<n> 最多维护 <n> 个节点连接(默认:125)-addnode=<ip> 添加一个节点以供连接,并尝试保持与该节点的连接-connect=<ip> 仅连接到这里指定的节点-irc 使用 IRC(因特网中继聊天)查找节点(默认:0)-listen 接受来自外部的连接(默认:1)-dnsseed 使用 DNS 查找节点(默认:1)-banscore=<n> 与行为异常节点断开连接的临界值(默认:100)-bantime=<n> 重新允许行为异常节点连接所间隔的秒数(默认:86400)-maxreceivebuffer=<n> 最大每连接接收缓存,<n>*1000 字节(默认:10000)-maxsendbuffer=<n> 最大每连接发送缓存,<n>*1000 字节(默认:10000)-upnp 使用全局即插即用(UPNP)映射监听端口(默认:0)-detachdb 分离货币块和地址数据库。会增加客户端关闭时间(默认:0)-paytxfee=<amt> 您发送的交易每 KB 字节的手续费-testnet 使用测试网络-debug 输出额外的调试信息-logtimestamps 调试信息前添加时间戳-printtoconsole 发送跟踪/调试信息到控制台而不是 debug.log 文件-printtodebugger 发送跟踪/调试信息到调试器-rpcuser=<用户名> JSON-RPC 连接使用的用户名-rpcpassword=<密码> JSON-RPC 连接使用的密码-rpcport=<port> JSON-RPC 连接所监听的 <端口>(默认:8332)-rpcallowip=<ip> 允许来自指定 <ip> 地址的 JSON-RPC 连接-rpcconnect=<ip> 发送命令到运行在 <ip> 地址的节点(默认:127.0.0.1)-blocknotify=<命令> 当最好的货币块改变时执行命令(命令中的 %s 会被替换为货币块哈希值)-upgradewallet 将钱包升级到最新的格式-keypool=<n> 将密匙池的尺寸设置为 <n>(默认:100)-rescan 重新扫描货币块链以查找钱包丢失的交易-checkblocks=<n> 启动时检查多少货币块(默认:2500,0 表示全部)-checklevel=<n> 货币块验证的级别(0-6,默认:1) SSL 选项: 1234-rpcssl 使用 OpenSSL(https)JSON-RPC 连接-rpcsslcertificatechainfile=<文件.cert> 服务器证书文件(默认:server.cert)-rpcsslprivatekeyfile=<文件.pem> 服务器私匙文件(默认:server.pem)-rpcsslciphers=<密码> 可接受的密码(默认:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) bitcoin.conf 配置文件除了 -datadir 和-conf 以外的所有命令行参数都可以通过一个配置文件来设置,而所有配置文件中的选项也都可以在命令行中设置。命令行参数设置的值会覆盖配置文件中的设置。配置文件是“设置=值”格式的一个列表,每行一个。您还可以使用# 符号来编写注释。配置文件不会自动创建;您可以使用您喜爱的纯文本编辑器来创建它。默认情况下,Bitcoin(或bitcoind)会在比特币数据文件夹下查找一个名为“bitcoin.conf”的文件,但是数据文件夹和配置文件的路径都可以分别通过-datadir 和-conf 命令行参数分别指定。 Windows:%APPDATA%\\Bitcoin 即C:\\Users\\username\\AppData\\Roaming\\Bitcoin\\bitcoin.conf Linux $HOME/.bitcoin/ 即/home/username/.bitcoin/bitcoin.conf 注意:如果Bitcoin 比特币客户端测试网模式运行,在数据文件夹下客户端会自动创建名为“testnet”的子文件夹。 bitcoin.conf 示例12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970# bitcoin.conf 配置文件。以 # 开头的行是注释。# 网络相关的设置:# 在测试网络中运行,而不是在真正的比特币网络testnet=0# 通过一个 Socks4 代理服务器连接proxy=127.0.0.1:9050################################################################ addnode 与 connect 的区别 #### #### 假设您使用了 addnode=4.2.2.4 参数,那么 addnode 便会与您的节点连接,并且告知您的节点所有与它相连接的其它节点。## 另外它还会将您的节点信息告知与其相连接的其它节点,这样它们也可以连接到您的节点。## #### connect 在您的节点“连接”到它的时候并不会做上述工作。仅它会与您连接,而其它节点不会。## #### 因此如果您位于防火墙后,或者因为其它原因无法找到节点,则使用“addnode”添加一些节点。## 如果您想保证隐私,使用“connect”连接到那些您可以“信任”的节点。 #### #### 如果您在一个局域网内运行了多个节点,您不需要让它们建立许多连接。## 您只需要使用“connect”让它们统一连接到一个已端口转发并拥有多个连接的节点。 ################################################################# 您可以在下面使用多个 addnode= 设置来连接到指定的节点addnode=69.164.218.197addnode=10.0.0.2:8333# 或使用多个 connect= 设置来仅连接到指定的节点connect=69.164.218.197connect=10.0.0.1:8333# 不使用因特网中继聊天(IRC)(irc.lfnet.org #bitcoin 频道)来查找其它节点noirc=0# 入站+出站的最大连接数maxconnections=# JSON-RPC 选项(用于控制运行中的 Bitcoin/bitcoind 进程):server=1 告知 Bitcoin-QT 接受 JSON-RPC 命令server=0# 您必须设置 rpcuser 和 rpcpassword 以确保 JSON-RPC 的安全rpcuser=bitcoinrpcrpcpassword=YourSuperGreatPasswordNumber_DO_NOT_USE_THIS_OR_YOU_WILL_GET_ROBBED_385593# 客户端在 HTTP 连接建立后,等待多少秒以完成一个 RPC HTTP 请求rpctimeout=30# 默认仅允许来自本机的 RPC 连接。在这里您可以指定多个# rpcallowip=,来设置您想允许连接的其它主机 IP 地址。# 您可以使用 * 作为通配符。rpcallowip=10.1.1.10rpcallowip=192.168.1.*# 在如下端口监听 RPC 连接rpcport=8332# 您可以通过如下设置使用 Bitcoin 或 bitcoind 来发送命令到一个在其它主机远程运行的 Bitcoin/bitcoind 客户端rpcconnect=127.0.0.1# 使用安全套接层(也称为 TLS 或 HTTPS)来连接到 Bitcoin -server 或 bitcoindrpcssl=1# 当 rpcssl=1 时使用的 OpenSSL 设置rpcsslciphers=TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTHrpcsslcertificatechainfile=server.certrpcsslprivatekeyfile=server.pem# 其它选项:# 设置 gen=1 以尝试生成比特币(采矿)gen=0# 预生成如下数目的公匙和私匙,这样钱包备份便可以对已有的交易以及未来多笔交易有效**keypool=100**# 每次您发送比特币的时候支付一个可选的额外的交易手续费。# 包含手续费的交易会更快的被包含在新生成的货币块中,因此会更快生效paytxfee=0.00# 允许直接连接,实现“通过 IP 地址支付”功能allowreceivebyip=1# 用户界面选项:# 最小化启动比特币客户端min=1# 最小化到系统托盘minimizetotray=1","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"设置","slug":"设置","permalink":"https://imzy.vip/tags/%E8%AE%BE%E7%BD%AE/"}]},{"title":"比特币交易基本问题","slug":"比特币交易基本问题","date":"2019-08-29T15:08:57.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/36076/","link":"","permalink":"https://imzy.vip/posts/36076/","excerpt":"经过一段时间的学习,尝试去理解比特币和区块链的概念,也在阅读rust-bitcoin项目下的代码。现在来提出一个比特币交易的基本问题:","text":"经过一段时间的学习,尝试去理解比特币和区块链的概念,也在阅读rust-bitcoin项目下的代码。现在来提出一个比特币交易的基本问题: 我们怎样去实现比特币交易?问题的产生问题的产生很简单,现在只有一个目的,想要在比特币网络上实现一次转账交易。在实现这个目标之前,我做了一些准备工作: 阅读了mastering bitcoin 参阅了 rust-bitcoin 项目下 rust-wallet的源码 阅读了bitcoinJ的源码,根据bitcoinJ的实现梳理出一个我认为的交易实现的过程 查阅了 rust-bitcoin 项目的子项目murmel,该项目是一个 SPV 节点的实现,截止目前还暂时无法运行 之前也在博客撰写了 比特币交易过程 的文章,可以在之前的博客看到。其中涉及熵的生成,助记词生成,秘钥的派生等钱包的概念。也在后续的文章中提炼出来交易过程中的脚本构建的过程。初步构建了比特币交易的流程。也有文章描述了SPV节点的概念,简单来讲 SPV 节点不存储完整的区块链信息,使用称为 merkle tree 的数据结构来验证交易信息。 初步思考第一层的准备工作完成之后,我阅读了 bitcoinj 的代码。bitcoinj 是使用 java 实现的比特币节点,它的功能非常齐全,包含全节点实现和轻节点(SPV)的实现。是受到 bitcoin core 推荐的一种实现。bitcoinj 使用的非常广泛,很多手机上的钱包都是基于bitcoin库的功能实现的。说一个和手机端最贴近的点:直接使用WalletAppkit 组件,他屏蔽了很多实现细节,暴露给开发者非常简洁的开发接口。具体的开发过程可以参考 bitcoinj 的文档,他已经非常简洁清楚了。此处提出一点建议,也是给自己的建议 阅读代码请从开发文档开始。先了解用法,然后再深入源码细节。 这个是最近阅读源码的经验之谈,这个道理也可以通用。 要是没有文档怎么办? 那就请从第二步开始,了解用法,从程序的启动入手。按照程序启动的顺序去梳理出代码的思路。这样也符合代码的开发流程,更容易体会代码开发过程和作者思路。 按照以上原则,我总结出了了 我以为的交易过程(也就是实现本文中目的的过程),注意以下的思路可能是 错误的,但这是我阅读bitcoinj和横向阅读rust-wallet的得到的结论,我认为思路是有价值的。 启动bitcoin节点,或者服务 根据参数去配置bitcoin network。bitcoin netwrok 主要分为三类 Mainnet 主网,默认端口 8333。 Testnet 测试网,默认端口 18333。 RegTest 私链,单机模式 节点探测,接入 bitcoin 的p2p网络,进行交互。节点(无论是轻节点还是全节点的一个重要工作就是维护这种p2p链接) 所有节点都有根据比特币协议所实现的功能,根据这些接口功能,用户组建交易信息,让节点去广播交易 再整理在此基础上,又根据《精通比特币》,中提到的方法有以下的流程 编译并启动比特币程序 使用 bitcore 提供的cli工具去 bitcore 交互,bitcore 本身提供 RPC 服务可以交互,这样就可以完成查询,挖矿,和交易。 根据这两方面的知识,去专门了解RPC服务是什么概念。因为 rust-bitcoin 中直接使用了 bitcoin-rpc 库。按照第一节的原则,整理RPC的用法。 https://github.com/rust-bitcoin/rust-bitcoincore-rpc 可以在以上的链接中查阅 rpc 库的用法 123456789101112extern crate bitcoincore_rpc;use bitcoincore_rpc::{Auth, Client, RpcApi};fn main() { let rpc = Client::new("http://localhost:8332".to_string(), Auth::UserPass("<FILL RPC USERNAME>".to_string(), "<FILL RPC PASSWORD>".to_string())).unwrap(); let best_block_hash = rpc.get_best_block_hash().unwrap(); println!("best block hash: {}", best_block_hash);} 可以说 rpc 的用法很简单,只需要提供三个参数即可,即 ip,username,password。这下就产生让我最大的疑问 ip从哪里来? username 和 password 从哪来? 以下是对问题的思考,他的结论是可能错误的,或者说是不完全的,但也是真实的思考过程 第一个问题: 还记的当初第一部分的思考吗,阅读了 bitcoinj 的代码所总结出的流程。启动节点时刻,本节点是空白的,并不知道该如何去和其他的节点建立 p2p 链接。根据比特币标准,比特币官方维护了 dnsseed,大概是一共就四到六个域名,只要解析这几域名就可以得到ip。事实上我确实使用 dns-resolve 得到了大量ip。 第二个问题: 查阅rpc的用法,username 和 password 是避不开的。在所有的比特币指南的开发中,链接比特币节点,username 和 password 都是需要的。阅读《精通比特币》书中的启动比特币节点,这个用户名是自己在配置文件中配置的。 错误的推理: 既然比特币官方提供dnsseed,确实能够解析出大量dns。而且考虑到比特币数据是公开的,那这些节点是不是开放的?不需要用户名和密码。 错误的流程: 基于以上错误流程,我总结出了要实现流程。去 dnsseed 上解析出ip,然后不用用户名和密码去和这些服务器建立 rpc 服务进行交互。 其实在这个推理过程之后,我还是去阅读了 murmel 的源码 ,跟着他一步步的去实现这个 spv 的节点,但是心中的困惑完全没有解决。再模仿 murmel 写了很多代码之后开始整理思路,这样做到底能否实现我要的交易。 矛盾的思考点出现了,我翻阅了比特币白皮书之类的资料。也对应查阅了很多具体的实现,发现没有一个这个做的。核心在于:比特币官方并不承诺提供RPC服务。也就是说解析出来这些 ip 地址是不可以进行 rpc 服务的。事实上我也尝试去链接这些 ip。确实没法办法链接!并且这些 rpc 服务在配置中是可以关闭的,比特币节点并不会承诺开放这种服务。 最后结论通过以上的过程,其实最大的问题就已经明白了。我们要完成交易,有哪些路径? 使用第三方提供的 api 比如bitchain 自己维护一个比特币节点,rpc 交互去交易 维护一个SPV节点 第一点不用说,第二点如果自己维护一个比特币节点,那么ip,username,password都是自己提供的,那只需要进行 rpc 链接。通过 rpc 服务完成交易即可。 维护SPV节点,比特币根据自己协议,轻节点根据自己的验证方式去验证的交易。(轻节点要与别的全节点交互) 最后顺带回答个小问题 整个交易中 RPC 服务在哪里,P2P 服务在哪里 我们把整个比特币的节点拆开看。分成三个部分,钱包,节点核心,别的节点。RPC 服务是节点对外界提供的功能(也可以不开启),用 RPC 的方式和比特币核心进行交互(比特币核心有多种实现,这个交互主要就是交易)。P2P则指的是比特币节点之间的链接方式,dnsseed 或者其他配置的节点链接发生在节点之间。至于为什么要分出钱包?因为我想实现个钱包去比特币核心上做交易。 注:中间那个错误的流程对于eth是可以的,因为 eth 没有轻节点,所以官方承诺提供这种服务,外界是可以连接和交互的。这也是 eth 被诟病不够去中心化的地方,事实上这个可以对外开放的节点也遭受了多次攻击。 完,如有错误,请指正。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"}]},{"title":"申请测试比特币的过程","slug":"申请测试比特币的过程","date":"2019-08-21T15:34:11.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/57093/","link":"","permalink":"https://imzy.vip/posts/57093/","excerpt":"接上文,在之前的流程图中明白了比特币交易的基本过程,现在需要使用是用测试比特币来完成一笔交易。本片文章就是完成交易的前置工作的记录——申请测试币。","text":"接上文,在之前的流程图中明白了比特币交易的基本过程,现在需要使用是用测试比特币来完成一笔交易。本片文章就是完成交易的前置工作的记录——申请测试币。 按照完整的流程,我们应该按照以下的过程完成 生成我们需要的私钥,和比特币地址。这样别人就可以把测试币发到你的地址上了。 去特定的faucet上申请测试币 1. 生成比特币测试地址建议去以下的网址生成比特币测试地址 比特币地址生成 需要特别注意的是,如果不加参数 ?testnet=true的话,生成的是正式地址。如果使用正式地址,faucet网站是不会给你打测试币的。 加参数的话请直接在链接的最后面加上这个参数,加上的话话出出现 TESTNET EDITION ACTIVED 请务必注意这一点。 还有一点需要仔细看的是生的的地址 以1开头的都是正式地址 ,以m或者其他开头的是测试地址 生成好记得保存。记得测试完成之后归还测试币。 2. 申请测试币发放测试币是社区或者其无偿提供的服务,所以很多发放地址会随着时间而实效,甚至于bicoinj上提供的地址在我用的时候已经实效了。下面列出我使用的两个地址 https://testnet-faucet.mempool.co/ 每次申请可以获取0.01个比特币。 https://kuttler.eu/en/bitcoin/btc/faucet/success/ 这个网站是运营者手动打测试币的,一般可以获取 0.1个测试币。如果比较着急的话,可以尝试给作者发邮件催一下。更详细的说明请参考网站说明。我是 https://bitnodes.earn.com/nodes/?q=China 这是一个比较好的比特币浏览器,可以很方便的查询交易信息。 另外随着时间的推移这些发币的网站也许不能用了,请使用faucet bitcoin关键字搜索。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"},{"name":"bitcoin","slug":"bitcoin","permalink":"https://imzy.vip/tags/bitcoin/"},{"name":"测试币","slug":"测试币","permalink":"https://imzy.vip/tags/%E6%B5%8B%E8%AF%95%E5%B8%81/"}]},{"title":"比特币交易过程二","slug":"比特币交易过程二","date":"2019-08-09T15:41:28.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/37280/","link":"","permalink":"https://imzy.vip/posts/37280/","excerpt":"接上一篇文章,这张星图描绘了比特币交易细节中的脚本。","text":"接上一篇文章,这张星图描绘了比特币交易细节中的脚本。 注意这也是后续比特币的继承者们对比特币不满意的地方。因为比特币交易脚本是基于栈堆的非图灵完备语言,后续的追求者希望在区块链的基础上运行强大的机器语言,让每个用户都可以见证其运行的过程的结果“智能合约”应运而生。当然,这里的图只简单指出比特币的交易脚本。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"}]},{"title":"比特币交易过程一","slug":"比特币交易过程一","date":"2019-08-07T16:13:53.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/62625/","link":"","permalink":"https://imzy.vip/posts/62625/","excerpt":" 根据Mastering Bitcoin梳理出的比特比交易过程,涉及公/私钥生成,钱包,和真正的交易过程,涉及较多细节。","text":" 根据Mastering Bitcoin梳理出的比特比交易过程,涉及公/私钥生成,钱包,和真正的交易过程,涉及较多细节。 因为设计细节较多,单纯做一张图已经难以阅读,(同时也因为节点太多绘图工具太卡了…)所以拆分开来。本文章的图从三点钟方向开始阅读,顺时针方向阅读。这次的星图只到交易脚本构建结束,后续的文章将从交易脚本构建开始描述。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"},{"name":"bitcoin","slug":"bitcoin","permalink":"https://imzy.vip/tags/bitcoin/"}]},{"title":"SPV","slug":"SPV","date":"2019-08-02T16:22:05.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/12108/","link":"","permalink":"https://imzy.vip/posts/12108/","excerpt":"SPV是什么何为SPV文章的开头首先要明确一个问题,什么是SPV? SPV是一个典型的区块链的领域知识,SPV的全称叫做 Simplified Payment Verification翻译成中文就叫做“简单支付认证”。","text":"SPV是什么何为SPV文章的开头首先要明确一个问题,什么是SPV? SPV是一个典型的区块链的领域知识,SPV的全称叫做 Simplified Payment Verification翻译成中文就叫做“简单支付认证”。 其实对应简单支付认证的是不是还有一个叫做复杂支付认证的东西呢?答案是肯定的。虽然说对应的知识叫做不叫做”复杂支付认证“但是我们还有有必要来了解其概念。 我们都知道,SPV是去中心化的分布式存储结构,一个完整的区块链节点包含以下四个部分(这种节点叫做full Node) W -> Wallet M -> Miner B -> Full Blockchain N -> (Network) Routing Node 以上四个节点分别代表了钱包,矿工,完整的取款链数据库和网络路由。虽然是分布式,完全公平的节点,但是受限于网络,机器等具体情况,并不是每个节点都具有完整的这四个部分。特别是Full Blockchain。一个完整的比特币数据库,在比特币网络不断膨胀的今天,已经有数百G之多。实际情况中,很多比特币的节点都运行在老旧的设备上,或者是移动设备上,没办法保存完整的比特币数据库。因为完整的比特币节点可以独立的完成所有交易的校验,对应这种不完整的比特币节点的校验,就就产生了SPV,也就是简单支付验证。 没有完整的比特币数据库的节点就叫做SPV节点,他的验证方式称之为SPV,这种节点相对于Full Node,被称之为轻量级节点。 SPV的认证方式区别于Full Node,SPV没有完整的比特币数据库,所以他在原理和认证方式上区别于全节点。在Mastering Bitcoin的8.8节,简单支付认证的一节中有这样一句话 SPV 节点只需下载区块头,而不用下载包含在每个区块中的交易信息。由此产生 的不含交易信息的区块链,大小只有完整区块链的 1/1000。 这个说明了,spv如何用更小的空间来保存交易信息。在SPV之前我们有必要回顾一下 区块的数据结构,一般来说一个完整的区块包含如下信息 数据项 字节大小 字段 说明 Magic NO 4 魔数 常数 Blocksize 4 区块大小 用字节表示的该字段之后的区块大小 Blockheader 80 区块头 组成区块头的几个字段 Transaction counter 1-9 交易计数器 该区块包含的交易数量,包含coinbase交易 Transactions 不定 交易 记录在区块里的交易信息,使用原生的交易信息格式,并且交易在数据流中的位置必须与Merkle树的叶子节点顺序一致 而SPV节点值保存其中的区块头,按照Mastering BitCoin的说法,只保存区块头,可以使的区块大小缩减到完整节点的1/1000以下。 回顾之后开始确认,如何进行SPV SPV 节点则不能验证 UTXO 是否还未被支付。相反地,SPV 节点会在该交易信息和它所在区块 之间用 merkle 路径建立一条链接。然后 SPV 节点一直等 待,直到序号从 300,001 到 300,006 的六个区块堆叠在该交易所在的区块之上, 并通过确立交易的深度是在第 300,006 区块~第 300,001 区块之下来验证交易的有 效性。 SPV的主要缺点就是只能验证一个交易是存在的,可以通过markle路径来验证,但是它无法验证一个交易是不存在的。 什么是Merkle Tree在此之前,先解释下上一节中提到的,“通过merkle 路径去验证”。那这个和markel tree有关的信息保存在哪? 答案:保存在区块头中。 区块头中有以下信息 Version Previous Block Hash Merkle Root Timestamp Difficulty Target Nonce 解释其中几个概念 区块链,顾名思义就是像链表一样的结构,所以这个Previous Block Hash指向前置区块的Hash,也就是指向父区块 Nonce属于挖矿的知识,可以自行查阅一下。(也叫随机数,是挖矿过程中控工暴力破解出来的随机数) 这里的Merkle Root就是构建Merkle Tree验证路径的关键因素。Merkle Tree是一个二叉树数据结构,不过多解释二叉树。Merkle Tree是在二叉树基础上添加了Hash,所以说这是一种哈希二叉树。 在比特币网络中,Merkle 树被用来归纳一个区块中的所有交易,同时生成整个交 易集合的数字指纹,且提供了一种校验区块是否存在某交易的高效途径。生成一 棵完整的 Merkle 树需要递归地对哈希节点对进行哈希,并将新生成的哈希节点插 入到 Merkle 树中,直到只剩一个哈希节点,该节点就是 Merkle 树的根。在比特 币的 Merkle 树中两次使用到了 SHA256 算法,因此其加密哈希算法也被称为 double-SHA256。 值得注意的是,具体的交易信息hash,都是储存在markle Tree的叶子节点上的,通过两两归并向上,直到归结成一个merkle root。(如果是奇数个节点,则最后一个节点的hash再复制一份,形成偶数节点。)所以一个merkle root就包含了区块中所有交易的信息。需要强调的另外一点是,哪怕是区块中有数万个交易最后求解出来的merkle root也只有32个字节。验证节点(也就是交易存在)的方法就是从root到子节点的搜索过程。 以上述构建的merkle树为例描述这个验证过程(这是个人理解的算法) 假定需要验证的交易是HK SPV向一个Full Node发送 Block信息和 TXID,查询这笔Txid是否存在在这个Txid里面,Full Node根据Block.Hash值,先找到这个Block,然后遍历Block的Transactions数组(这一点请参考之前的Transactions的解释),可以理解为遍历Transaction.Txid是否一样,如果没有,就直接返回SPV交易不存在,如果有的话,那么就要根据上图的信息,传输HL、HIJ、HMNOP 和 HABCDEFGH的值 所以在难度为logn的状态下验证了交易存在。 相关概念Bloom.下一篇文章介绍相关概念,布隆过滤器。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"SPV","slug":"SPV","permalink":"https://imzy.vip/tags/SPV/"}]},{"title":"跨域问题","slug":"跨域问题","date":"2019-07-15T10:57:30.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/48274/","link":"","permalink":"https://imzy.vip/posts/48274/","excerpt":"之前写的安卓程序,并不存在跨域这种说法,是可以自由的从不同的域名下请求数据的,现在看前端知识,则就出现了跨域这个概念。了解跨域,主要清楚以下的知识点就可以。","text":"之前写的安卓程序,并不存在跨域这种说法,是可以自由的从不同的域名下请求数据的,现在看前端知识,则就出现了跨域这个概念。了解跨域,主要清楚以下的知识点就可以。 什么是同源策略,满足同源策略的三个条件是什么 跨域的具体方案是什么,怎么处理。 什么是同源策略,满足同源策略的三个条件是什么浏览器为了安全起见,制定了同源策略,只允许在本域名下的数据请求操作,同源策略主要要求以下三个条件 协议相同,注意http和https是不同的协议 域名相同 端口一致 请求的url和当前html的url满足以上三个条件就是同源的,非同源的情况下,使用数据就会受到限制。 跨域的具体方案是什么,怎么处理实际场景中,我们不可能什么东西都放在自己的域名下来处理,比如我们可能需要一个天气请求的API,但是我们的主业是电商,那我们很可能就需要一个第三方的API来做。这个时候我们就不得不跨域。目前主流的跨域的方案有一下三类。 JSONP JSONP是JSON with padding的简称。我们知道 Script 标签中的js不受同源策略的影响,我们可以自由加载任何域名下的js来执行。JSONP就是以此为契机建立的。具体的我们在执行的时候可以这么做 在html端 12345<script src = "https://a.imzy.me?callback = getWeather"> function getWeather(data){ console.log(data) }</script> 在服务端返回的时候 带上 callback({data: xxx})。 前后端配合,实现JSONP。不过以上的做法显得有些tricking。最主要的缺点是他只可以进行get请求。 CROS CROS的意思是跨域资源共享,这种跨域的方法是对ajax的拓展。 CROS机制是浏览器发现请求的链接和当前页面url是不同源的,就主动在请求头中添加一个Origin的头,然后后端设置一个Access-Control-Allow-Origin的响应头。响应头中带有服务端允许的域。允许的域和Origin中的域能对应上就可以使用CROS。主要需要后端设置响应头。 详细解释下,Origin头说明的是当前请求来自于哪个域,ACAO则说明的是服务端允许的域。 可以参考 CROS. 和iframe有关的行为 在html页面上,我们也可以使用iframe来嵌套另一个html页面。我们的主页面,一般是不允许控制iframe中的元素的,除非iframe和我们的主页面是同一个域。不过iframe确实存在需要跨域的场景。和iframe有关的跨域主要分为以下两种 降域。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546a.html<html><style> .ct{ width: 910px; margin: auto; } .main{ float: left; width: 450px; height: 300px; border: 1px solid #ccc; } .main input{ margin: 20px; width: 200px; } .iframe{ float: right; } iframe{ width: 450px; height: 300px; border: 1px dashed #ccc; }</style><div class="ct"> <h1>使用降域实现跨域</h1> <div class="main"> <input type="text" placeholder="http://a.nany.com:8080/a.html"> </div> <iframe src="http://b.imzy.vip:8080/b.html" frameborder="0" ></iframe></div><script>//URL: http://a.imzy.vip:8080/a.htmldocument.querySelector('.main input').addEventListener('input', function(){ console.log(this.value); window.frames[0].document.querySelector('input').value = this.value;})document.domain = 'imzy.vip'</script></html> 以下是iframe中的代码 123456789101112131415161718192021<html><style> html,body{ margin: 0; } input{ margin: 20px; width: 200px; }</style> <input id="input" type="text" placeholder="http://b.imzy.vip:8080/b.html"><script>// URL: http://b.imzy.vip:8080/b.html document.querySelector('#input').addEventListener('input', function(){ window.parent.document.querySelector('input').value = this.value;})document.domain = 'imzy.vip';</script></html> 注意以上代码中,a.imzy.vip和b.imzy.vip是不同的域名。但是他们的后缀是一样的,所以可以降域为imzy.vip PostMessage 主要使用的是iframe的postMessage方法,来进行iframe的通信。 12345678910111213141516171819202122232425262728293031<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>跨域POST消息发送</title> <script type="text/JavaScript"> // sendPost 通过postMessage实现跨域通信将表单信息发送到 指定的域名上, // 并取得返回的数据 function sendPost() { // 获取id为otherPage的iframe窗口对象 var iframeWinow = document.getElementById("otherPage").contentWindow; // 向该窗口发送消息 iframeWin.postMessage(document.getElementById("message").value, 'http://imzy.vip'); } // 监听跨域请求的返回 window.addEventListener("message", function(event) { console.log(event, event.data); }, false); </script> </head> <body> <textarea id="message"></textarea> <input type="button" value="发送" onclick="sendPost()"> <iframe src="http://imzy.vip/other-domain.html" id="otherPage" style="display:none"></iframe> </body></html> 放在另一个域名下的iframe内容 123456789101112131415161718192021222324252627282930<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>POST Handler</title> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <script type="text/JavaScript"> window.addEventListener("message", function( event ) { // 监听父窗口发送过来的数据向服务器发送post请求 var data = event.data; $.ajax({ // 注意这里的url只是一个示例.实际练习的时候你需要自己想办法提供 type: 'POST', url: 'http://imzy.vip/getData', data: "info=" + data, dataType: "json" }).done(function(res){ //将请求成功返回的数据通过postMessage发送给父窗口 window.parent.postMessage(res, "*"); }).fail(function(res){ //将请求失败返回的数据通过postMessage发送给父窗口 window.parent.postMessage(res, "*"); }); }, false); </script> </head> <body></body></html> 以上是通过PostMessage来进行iframe通信的实例,主要是完成了,从主界面发送消息到子iframe,子iframe中监听消息,将数据使用window.parent.postMessage()方法,回传数据的过程。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://imzy.vip/tags/%E5%89%8D%E7%AB%AF/"},{"name":"技术","slug":"技术","permalink":"https://imzy.vip/tags/%E6%8A%80%E6%9C%AF/"}]},{"title":"再读编程之道-类型系统","slug":"再读编程之道-类型系统","date":"2019-06-20T12:06:57.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/55711/","link":"","permalink":"https://imzy.vip/posts/55711/","excerpt":"快速再读编程之道,做了如下一张思维导图","text":"快速再读编程之道,做了如下一张思维导图 注意几个术语 RHS 代表符号右边的值 关联类型:trait中出现的type 标记,必须指定type为指定类型 <RHS = Self> 是指定RHS的默认类型为Self(实现该类型的具体类型) Turbofish操作符 Ad-hoc多态 ?Sized表示 的是 Sized和UnSized的总和。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"Rust","slug":"Rust","permalink":"https://imzy.vip/tags/Rust/"},{"name":"编程之道","slug":"编程之道","permalink":"https://imzy.vip/tags/%E7%BC%96%E7%A8%8B%E4%B9%8B%E9%81%93/"}]},{"title":"JavaScript 中的if() 和 == 的奇怪行为","slug":"JavaScript-中的if-和-的奇怪行为","date":"2019-06-14T16:36:50.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/33446/","link":"","permalink":"https://imzy.vip/posts/33446/","excerpt":"JavaScript真是一种奇怪的语言。 开头就是这么一句对JS性质的限定,因为之前都使用的是静态语言,强类型语言,所以对于if() 和 ==这种简单的语法并没有必要去写一篇文章,但是js的天生缺陷确会产生一些很奇怪的行为。所以特地写一篇文章来记录这些行为。","text":"JavaScript真是一种奇怪的语言。 开头就是这么一句对JS性质的限定,因为之前都使用的是静态语言,强类型语言,所以对于if() 和 ==这种简单的语法并没有必要去写一篇文章,但是js的天生缺陷确会产生一些很奇怪的行为。所以特地写一篇文章来记录这些行为。 语义首先解释语义 if() 用于条件判断 其中括号中需要一个boolean 表达式 == 用于判断 得到的结果是一个布尔表达式, 在js中是不严格判断,会先进行类型转换,然后进行比 if()的奇怪行为1234567891011if("a"){ console.log("a")}if(""){ console.log("hello")}if([]){ console.log("world")} 以上的代码会怎么执行呢。可以直接去任意一个浏览器运行这些代码,查看结果。这里先抛出结论 JavaScript会把括号中的 东西转化为布尔值,之后根据结果执行 其中遵循的结果如下 类型 转换形式 Undefined false Null False Boolean 自己体会 number 0 +0 -0 NaN为false 其他为True String 空字符串””为false 其他为true Object True 所以带入就好了。根据这个原则 第一第三都可以输出,第二个是无法输出的。 那忠告是什么呢? 别这么写好吗,老老实实去写布尔表达式。 == 的奇怪行为12345678910"" == 0" " == 0"" == true"" == false" " == false!" " = false!" " = true"hello" = true"0" = true 以上会产生什么结果? emmm。。。 这我怎么知道。只能说JS真会玩 好了先上结论 x Y Result null undefiend true Number String x==toNumber(y) Boolean Any tonumber(x) == y Object String or Number toPrimitive(x) == y Otherwise Otherwise False 解释一下什么叫toPrimitive就是转为基本类型,特就是调用valueOf()方法和 toString()方法。valueOf的优先级比toString的优先级高。 又有人问了,那数字怎么转 Type Result Undefined NaN Null 0 Boolean True -> 1, false -> 0 String “abc” -> NaN, “123” -> 123, “” -> 0 那重点是什么呢 遇到 == ,就尝试给等号两边的表达式,变量转化为数字,转为数字之后再比较。 也有特例 12"" == 0 // true" " == 0 // true 按照上面说法字符串转过去其实应该是NaN啊 怎么就true了呢,说白了,如果没有这个例外,js能叫做奇怪的语言吗?所以假装他转成0了。然后去控制台试试,奥,他确实转成0了。所以表上加一条,空字符串,空白字符串(这是两个东西都是转成0) 好了有了以上的原则,转的再奇怪也不怕了。等等那下面呢 1!" " == true 分析一下 ,” “转化为0, 那 !0是多少? 不对劲啊,这我怎么分析。机智的思考之后,这个牵扯到优先级问题,打开mdn,搜索运算符优先级。看来取非的优先级高,那我看看左边是个什么 ,别猜顺序的了,直接去控制台看看。我们得到!”” ->false。 那这个结果只能是false了 。0 和 1肯定不能相等了。 好了,以上就是这些奇怪的行为的总结。最后的忠告是什么呢? 能不能把代码写清楚点!别搞这些有的没的,这么写会被打死 伟大的编程著作都告诉我们了,代码写的别人都看不明白,你想干啥!请听大师的劝告!","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://imzy.vip/tags/JavaScript/"},{"name":"前端","slug":"前端","permalink":"https://imzy.vip/tags/%E5%89%8D%E7%AB%AF/"},{"name":"if() 和 ==","slug":"if-和","permalink":"https://imzy.vip/tags/if-%E5%92%8C/"}]},{"title":"二叉树的遍历方法","slug":"二叉树的遍历方法","date":"2019-05-28T19:28:21.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/60409/","link":"","permalink":"https://imzy.vip/posts/60409/","excerpt":"最近刷算法,补充算算法知识,因为本人的基础薄弱,补起来比较吃力。二叉树的遍历方法,非递归版本的遍历方法让我迷惑,但是我读到了一篇写的非常精彩的文章 https://zhuanlan.zhihu.com/p/30490183 ;有兴趣的可以去参考下原文。","text":"最近刷算法,补充算算法知识,因为本人的基础薄弱,补起来比较吃力。二叉树的遍历方法,非递归版本的遍历方法让我迷惑,但是我读到了一篇写的非常精彩的文章 https://zhuanlan.zhihu.com/p/30490183 ;有兴趣的可以去参考下原文。 我在这里总结出核心思想。 对于二叉树中的任何一个节点而言,它都有两个角色需要扮演,一个是作为值存储的角色(角色1),另一个角色是作为它所带领的子树的一个代表(角色2)。而我们设置的boolean变量,就是为了说明我当前拿到的这个节点,应该是以一个值存储的这种角色对待它(True),还是应该以一个子树的代表这种角色对待它(False),如果是前者,那么就简单的将其所存储的值打印出来,如果是后者,我们需要继续探索由它带领的子树。 通过这个核心思想,我们可以将二叉数的遍历方法做一个统一的规划了,我们先包装Node,让他带一个是否访问过的标志,(如果你不用Java,比如py,你可以直接用元组)。 ` 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */class Solution { class TreeNodeWhithBoolean { TreeNode node; boolean visited; public TreeNodeWhithBoolean(TreeNode node,boolean visited){ this.node = node; this.visited = visited; } } public List<Integer> preorderTraversal(TreeNode root) { if(root == null){ return new ArrayList(); } List<Integer> list = new ArrayList<Integer>(); TreeNodeWhithBoolean rootBoolean = new TreeNodeWhithBoolean(root, false); Stack<TreeNodeWhithBoolean> stack = new Stack<TreeNodeWhithBoolean>(); stack.push(rootBoolean); while(!stack.isEmpty()){ TreeNodeWhithBoolean current = stack.pop(); if(current.visited){ list.add(current.node.val); }else { if(current.node.right != null){ stack.push(new TreeNodeWhithBoolean(current.node.right, false)); } if(current.node.left != null){ stack.push(new TreeNodeWhithBoolean(current.node.left, false)); } stack.push(new TreeNodeWhithBoolean(current.node, true)); } } return list; } } 上面这段是前序遍历的代码,中序遍历和后续遍历的代码也大同小异,只需要替换入栈顺序即可,因为栈为我们提供了一个天然翻转的工具。 比如前序遍历的顺序是 根,左,右。也就是说出栈顺序就是这个,那入栈顺序反过来就好了。其他的遍历也是完全按照这个思路来。","categories":[{"name":"算法","slug":"算法","permalink":"https://imzy.vip/categories/%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"技术","slug":"技术","permalink":"https://imzy.vip/tags/%E6%8A%80%E6%9C%AF/"},{"name":"遍历","slug":"遍历","permalink":"https://imzy.vip/tags/%E9%81%8D%E5%8E%86/"}]},{"title":"关于em和line-height的备忘录","slug":"关于em和line-height的备忘录","date":"2019-05-16T12:30:03.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/4005/","link":"","permalink":"https://imzy.vip/posts/4005/","excerpt":" 点了点前端技能,现在在网上看到好多关于em和line-height的博客,不客气的说很多博客都是错误的,或者所谓的深入浅出都是不知所云,所以写一个备忘录来提醒自己。","text":" 点了点前端技能,现在在网上看到好多关于em和line-height的博客,不客气的说很多博客都是错误的,或者所谓的深入浅出都是不知所云,所以写一个备忘录来提醒自己。 关于line-heightline-height:2 和line-height:200%到底有什么差别 他们基于的标准都是一样的,都是基于自身的倍数。区别在于继承性上,当父元素用数值的话,子元素继承得到的都是这个数值 ,当父元素使用百分比,父元素会基于其百分比算出有多少px,然后在继承下去。 换个更容易理解的话叫做:看有没有单位(百分比也算单位),没有单位相当于继承系数,子元素继承系数后(根据自己的字体大小)算出自己的行高。有单位的话,父元素是计算出自己的行高,把具体的数值(像素)继承给子元素。请查看以下代码自行体会。 https://jsbin.com/fusinih/edit?html,css,output 关于emem.rem都是相对字体的大小 em是相对当前元素字体的大小 只有当当前的font-size使用em做大小单位的时候是依据 父元素字体的大小来做的的。这个解释可以参考MDN。 以下是我的说明 因为em要基于当前的字体大小,当前的fontsize没设置大小,则字体大小只可以从父元素继承,自然就是父元素大小的倍数。","categories":[{"name":"技术","slug":"技术","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://imzy.vip/tags/%E5%89%8D%E7%AB%AF/"},{"name":"技术","slug":"技术","permalink":"https://imzy.vip/tags/%E6%8A%80%E6%9C%AF/"}]},{"title":"浏览网页时,发生了什么","slug":"浏览网页时-发生了什么","date":"2019-05-11T16:03:41.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/36226/","link":"","permalink":"https://imzy.vip/posts/36226/","excerpt":" 之前一段时间,阅读了计算机网络,对于每个章节都做了一个相应的思维导图来描绘计算机网络的七层模型(或者说5层模型),现在写一篇简答的博文来来回答一个简单的问题:当我们浏览网页的过程中发生了什么?一个典型的浏览的过程是什么呢?简单的感受就是,在浏览器的地址栏输入网址,比如www.imzy.vip ,然后呈现的出该网站的界面。当然访问每一个网页,抽象出来的过程都是一致的主要简单划分为以下几个过程。","text":" 之前一段时间,阅读了计算机网络,对于每个章节都做了一个相应的思维导图来描绘计算机网络的七层模型(或者说5层模型),现在写一篇简答的博文来来回答一个简单的问题:当我们浏览网页的过程中发生了什么?一个典型的浏览的过程是什么呢?简单的感受就是,在浏览器的地址栏输入网址,比如www.imzy.vip ,然后呈现的出该网站的界面。当然访问每一个网页,抽象出来的过程都是一致的主要简单划分为以下几个过程。 网址和协议 在浏览器界面上的地址栏输入的东西,比如http://www.imzy.vip,整个地址就叫做网址,网址的构成如下 1协议://域名::端口号/资源地址 形如http的部分就是协议名称,http协议被称为超文本传输协议,对应的还有file协议,如果用浏览器打开本地计算机上的对应文件,html之类的就会用到file协议。另外一种常见的协议就是ftp协议,在http协议之前,ftp是网络时间上最常见的协议,用于文件的传输。 端口号是指::8080这种形式,其中http默认的端口号是80,这种默认的端口号可以不用填写。 之后用斜杠跟着的是资源位置,不再赘述。 DNS的解析浏览器中输入的域名经过寻址,才能找到指定的服务器,但是服务器的地址是ip地址,形如xx.xx.xx.xx 这里的xx指的10进制的数字。这种是典型的ipv4地址。从域名www.imzy.vip 到xx.xx.xx.xx的转换就称之为域名的解析。域名的解析,简单来说就是需要域名解析服务来解析,根据网络建设的架构,全球分布着数台顶级域名解析器。需要注意的是,域名用点分发记录,越高级的域名排在最后。比如我的域名vip是顶级域名,常见的顶级域名还有 .cn .com .org .gov等。详细的知识可以参考谢希成的计算机网络一书,截止目前这本书已经发布了第七版。堪称与时俱进。 DNS的解析是按顺序的,不是所有的网络请求都直接去顶级域名服务器申请解析的,主要的解析按照以下顺序进行 浏览器缓存 系统(指计算机操作系统OS)的HOST 路由器(按照计算机网络一书,路由器也算是网络世界中的计算机,不过他承担的任务特殊罢了) ISP服务商,既提供给你网络服务的电信服务商的缓存,比如中国电信提供的DNS解析服务 根域名服务器 从上到下六个部分都可以进行域名的解析,哪一步能完成就到哪一步进行,域名和ip的转换就在那里进行,不去下一步。这样也减小了域名解析的压力。 由此产生了两个小问题如下 为什么不直接用ip,减少域名解析的步骤呢? DNS劫持指什么 关于两个问题的回答如下 其一,使用域名的解析是为了语义化,所谓的语义化,简单理解就是贴近自然语义,方便人的理解。比如我们访问百度,直接说访问百度,或者说访问www.baidu.com,如果没有域名,直接说访问202.108.22.5。 直接对比一下,那个好记录呢。可以说域名是人记忆力的妥协,毕竟对于人来说,词才是有意义的,数字并无直接意义。对于计算机来说,点分十进制和域名的记录都是一样的。工具为人服务,自然要向人妥协 其二,DNS劫持。DNS的解析就是拿到域名之后解析成为ip的过程,所谓劫持,就是扰乱这个过程,访问百度域名,给了你一个非百度的ip,用户这边看到地址栏还是百度的域名,背后发生了什么一无所知。DNS劫持是一种后果非常严重的不安全行为,假如你要访问淘宝买东西,但是你的DNS被劫持了,指向了非淘宝的地址。这个过程一想,就会造成非常严重的经济损失。当然,还有其他的损失。一般的DNS劫持都发生在路由器,当然ISP服务商也有可能会劫持DNS。之前闹的沸沸扬扬的小米路由器DNS劫持事件,也是一个非常热门的话题。 补充一点,我们平时填写的DNS服务器,8.8.8.8 或者 114.114.114.114以及最近比较火的1.1.1.1都是域名解析器的地址。 服务器网络请求经过DNS解析,找到对应的服务器ip,通过路由器的层层转发,到达了服务器,算是真正进入了后端控制的部分了。一般把用于web服务的服务器都叫做 Web Server。后文把它简称为WS。WS简单来说就是安装了服务器软件的电脑,常见的Web服务器的操作系统主要是Linux和Windows Server。对应的服务器的软件有Apache,Nginx,IIS,Lighttpd等。这种OS+软件,就称之为WS。WS主要是为了接受请求,或者反向代理到其他的WS。一台WS上可能运行有不同的服务器程序(这个就是程序开发者自己写的),这些软件也负责吧不同的请求分发到对应的处理程序上去。 网站的处理流程经过长时间的游荡,请求终于到了程序猿自己掌控的时间了。以常用的Ruby on rails为例。我也在之前的博文中写过,如何使用Actix-web来写一个web程序,之前也参考过Ruby on rails。典型的网路处理流程是MVC模式的。这里的Modle指数据部分。V指view,显示层简单来书哦就是html界面。C就是控制层,controller,用来粘合这两个层层面,直白的说就是根据不用的请求从model拿数据,交给view层渲染(view需要数据来渲染)。 更具体点的过程是这样的,一个请求过来,经过router,分配给对应的controller。controller处理对应的逻辑,从model拿数据,交给view层来生成界面。最后把生成好的界面交给用户。一个题外话,一个合理的架构中,处理逻辑的controller和处理数据的model,应该是独立的。也就是说Data Server应该独立于单独web服务之外。更多对于mvc的理解是基于编程理解的。也不是本文的主要讨论过程。有兴趣的可以去看我用rust写的Actix-web博客,麻雀虽小,但是整个框架是完整的。 浏览器解析服务器处理之后,终于交给用户了。简单来说就是说吧html交给浏览器。浏览器就会解析html和css,生成我们看到的界面。","categories":[{"name":"技术","slug":"技术","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"前端","slug":"前端","permalink":"https://imzy.vip/tags/%E5%89%8D%E7%AB%AF/"},{"name":"网络","slug":"网络","permalink":"https://imzy.vip/tags/%E7%BD%91%E7%BB%9C/"}]},{"title":"思维导图心得","slug":"思维导图心得","date":"2019-05-10T11:24:47.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/13602/","link":"","permalink":"https://imzy.vip/posts/13602/","excerpt":"缘起 我在很久以前就接触过思维导图的概念,毕业工作的这几年自己读书的时候也是广泛使用过思维导图这一工具。作为英国人东尼赞博发明的工具,风行与世界已经有数十年之久。除了我现在看的这一本叫做《思维导图》的书,在此之前还有另一个版本叫做《思维导图宝典》,也算是一本书的不同翻译版本。我之前也读的是这一本。东尼赞博也在书中提到过,思维导图本身并非书籍意义的复刻,所以我以下的分享也不按照本书的结构来,主要介绍个人感受。","text":"缘起 我在很久以前就接触过思维导图的概念,毕业工作的这几年自己读书的时候也是广泛使用过思维导图这一工具。作为英国人东尼赞博发明的工具,风行与世界已经有数十年之久。除了我现在看的这一本叫做《思维导图》的书,在此之前还有另一个版本叫做《思维导图宝典》,也算是一本书的不同翻译版本。我之前也读的是这一本。东尼赞博也在书中提到过,思维导图本身并非书籍意义的复刻,所以我以下的分享也不按照本书的结构来,主要介绍个人感受。 思维导图的科学依据 书籍的最前面的章节从大脑的左右半脑的原理,格式塔,大脑神经突触的部分大量描述,试图阐述思维导图的科学依据。本书有许多经典的论述,比如描述大脑有一万亿个神经元构成,由像章鱼一般的电化学结构神经元互相链结。(当然这个是基于科学研究)文章又批判了非常流行的科学观念,左右脑的科学。原文中提到过,简单的把人分为左脑人和右脑人是在限制自己开发策略的能力(P829 或者卜煜婷版本P9)。还有就是作者的格式塔的概念,即大脑拥有补充完整完整的固有倾向。可以说三个观点可以被称之为思维导图的科学依据。这三个理论,其中神经细胞的形象来源于科学进步,本身无可辩驳,不过对于我来说,他确实和思维导图的科学联系有限。左右脑理论甚至在60年代获取了诺贝尔奖。至于格式塔的理论,应该则是20世纪初提出的一个心理学的流派(源于德国)。作者在早期的书籍中,引用了大量的数据,实验来佐证思维导图的合理性。有趣的是后期的版本,作者逐渐删去了这些科学实验,数据,对于这三个理论也只是简单介绍,不再过多阐述他和思维导图的科学关联。甚至于我后来购买的化学工业出版社版本的《思维导图》把书籍归于成功励志类。这也算是对其科学性小小的怀疑吧。 虽然说了这么多,但并不是说思维导图是不科学的。思维导图确实是一种非常强大的学习工具,我在生活和学习中受益良多。之前的考据,也是在作者鼓励发散思考时做了一点考据工作,权当个人趣味。 感性与理性 这个部分叫做感性与理性,或许也可以叫做抽象和具体。思维导图作为一种读书和思维工具,在我固有的印象中他应该是偏向理性,抽象的部分。重读书籍的过程中,我逐渐抛弃了这种方法。按照书中的论述,思维导图要兼顾,主次,图像,色彩。使思维导图迈向艺术作品的门槛。书中反复提及的天才达芬奇,我有幸见过他笔记的真迹,确实如作者所说,图片繁多而精美。可见天才也是超越时代的,在线性记忆的时代,已经超脱局限,使用非线性的方式来记录自己的思维。使人惊叹于思维的理性和美感的完美结合。文章中另一个和感性有关的地方就是思维导图的应用过程中,情感,或者感觉的重要性。不在过多阐述二分法或者加减法的决策。作者在决策遇到难题时,比如数字法的左右的得分一样情况下该怎么做决定。作者给出的答案很简单,依据累心的感觉。作者给内心的感觉起了一个很好听的名字叫做”超逻辑“,感觉比逻辑性逻辑更强。甚至引入一组数据来证明这种遵循内心感觉的正确性以及令人赞叹的成功率。这应该是全书中我最感兴趣的一部分。以后我也可以放心大胆的追求内心的声音了。 应用 本书也应用了大量的案例来说明思维导图在日常的生活中如何使用,我不会在这里重复说明。等会我上传一种我在读这本书之前做的一张简单思维导图,内容是王潇的新年课程。 未来智慧 作为头顶着耀眼光环和非常长头衔的专家,东尼赞博在书的最后一部分大胆预测了未来智力革命。作者描述到未来的图景是思维的普及,个人及家庭,组织和社会乃至我们整个文明的思维普及,在作者写书的年代,已经有很多的思维机构,比赛开始关注元学习,如何思考。我查过,比较早的版本出版于1996年,其实作者对于未来的描述,这个未来已经到来了。现今的知乎,我们常见的逻辑思维,跨年的时候办的思维演讲会也算是作者提到的思维革命的具体化。未来已来 最后真心希望一起和我读书的小伙伴,透过这本小册子,理解思维导图的起源和使用方法,多多使用这一强大的工具。在求知之路上更进一步。共勉! 2019年1月4日 00:48(旧文搬运)","categories":[{"name":"读书","slug":"读书","permalink":"https://imzy.vip/categories/%E8%AF%BB%E4%B9%A6/"}],"tags":[{"name":"思维导图","slug":"思维导图","permalink":"https://imzy.vip/tags/%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE/"}]},{"title":"雪球速度法的感悟","slug":"雪球速度法的感悟","date":"2019-05-10T11:02:08.000Z","updated":"2024-08-12T16:01:47.538Z","comments":true,"path":"posts/34949/","link":"","permalink":"https://imzy.vip/posts/34949/","excerpt":"旧文搬运,一份迟到的速读法感悟","text":"旧文搬运,一份迟到的速读法感悟 几周之前我已经连续花了两个下午读完了这本《雪球速读法》但是我有些别的事情,并没有及时写下这篇速读法的感悟,现在赶上写这么一篇。 说实话,我再读这本书之前,对于速读这个概念根本不敢兴趣。我向来的理念就是读书急不得。所谓读书,就是要全身心的阅读,感悟,去体悟作者描绘的概念,体悟作者的思想。然后加入自己的思考。对于这么一个复杂的过程,怎么可以草草了事呢?所以我向来认为读书急不得。速读法这种东西,我向来认为这是一种”邪道”。另一个方面,我从小很少受到阅读速度的制约。因为我从小就很喜欢读书,读书速度就不慢。我记得自己还在上小学的时候,和我爸爸一起看各种神秘世界的小册子,我的速度就已经超过他了。读书时代,朋友问我读书速度为什么要快一些,这个问题我都是无法作答的。 不过作为列在2019书单上的书,我还是从淘宝上购得一本,去看看我不了解的“速读”是个什么概念,满足一下我的好奇心。这本速读法,其实也没什么实质性的概念,我读完全书,我的理解就是“多读书,就读的快”。作者在文章中提到了许多别的流派的读书方法,比如加大眼睛扫描的区域,快速过。我是非常不屑于这种读书方式的。人不是复印机,加大个扫描范围就可以实现更多行的文字处理了。更何况这种读书还寄希望于大脑自己会处理。我和作者在这方面的想法是完全一致的:怎么能寄希望于仓库自己处理货物呢。这种方法读书,就算很快读完,书在脑子里恐怕都成了堆积的货物了。完全违背了读书的初衷。作者的自己提出的读书方案就显得科学的多了,和我日常的读书就有很多相似之处,我单独的把它提炼出来说书我的感受。 1.多读书就读的快 作者自己参加了一个读书会,发现到会的会员们自己都没有随身携带书籍很是震惊。说起来,这真是是个朴素的真理:读的多就读的快。我想按照这个说法,我完全可以回答我的同学为什么读的快:因为读的多。 2.建立自己的资料库 新奇却又是朴素的观点。可能取决于作者的观点,读书要建立自己的资料库,读的书在自己的资料库之中的就读的快。比如医学生读医学方面的就读的快。这一点按照我的思考就是,多思索,多读。读书的时候先了解相关的背景知识,也算是建立自己的读书资料库。另外一方面,读一本全新领域的书籍怎么建立自己的资料库,那就是读作者的前言,后记,录。我平时虽然不会把这种阅读方式称之为建立资料库我也是这么做的。作者经常会把自己写书的初写在前言或者后记中。万千事物都有自己的起点,都有自己要解决的根本问题,有自己的根,写作的初衷,根往往就写在前言中。或者再后记里,作者会把写完整本书的感想写出来。把握书的”根“。无论用那种说辞来描绘,依然是读书最重要的起点。另外就是读书的目录。目录即为框架,建立整本书的知识层次和框架就靠阅读书籍的目录。我平时也把书的目录称为脊椎。把握目录,即是把握脉络。说法不同,但其核心一脉相承,也是读书的最重要的准备工作。 3.多读几遍 这本书中提出了我之前经常犯的谬误,书要一遍读好。其实书的问题,特别是新领域的书籍,一遍是远远不够的。在书中读出疑惑是非常好的事情,但是被疑惑所困就造活得不偿失。因为书要一次读好,不厌其烦的追求其中的细节就会拖慢我们读书的脚步,甚至使我们失去读书的耐心,造成最终的放弃。”好读书,不求甚解“就是这个意思。读了一遍我们心中产生了疑惑,这个以后并不是要在当下立即解决。带着疑惑继续读,也许后文会有解释。或者再读一遍,就会有新的感悟。多读几遍,不要无限纠结于细节。对我来说这是读雪球法对我意义最重大的部分。以后的读书中,我看来还是要多念几遍”好读书,不求甚解“来提醒自己:要带着疑惑再多读几遍。 4快速读书,更专心,效率更高 这是书中观点中我赞同度不高的部分。作者认为,快速读书可以保持读书的专一性,避免了一本大部头书老是读不完造成最后的放弃。我再读书中似乎不存在这个问题,因为对于我定的大部头书籍我也会尽量读完。因为我的目标就是专注当下。读书的时候全身心投入,尽自己最大努力读完。我把这点提炼出来,如果有谁看到这部分,可以继续看看作者的说法。我认为这个不无道理,但对我意义有限。 2019愿你读书快乐!2019年2月6日 12:15","categories":[{"name":"读书","slug":"读书","permalink":"https://imzy.vip/categories/%E8%AF%BB%E4%B9%A6/"}],"tags":[{"name":"阅读","slug":"阅读","permalink":"https://imzy.vip/tags/%E9%98%85%E8%AF%BB/"}]},{"title":"如何用Rust写一个自己的博客","slug":"如何用Rust写一个自己的博客","date":"2019-04-06T08:53:40.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/44906/","link":"","permalink":"https://imzy.vip/posts/44906/","excerpt":"最近给自己定下一个任务,用rust写一个可以运行的项目,最终定下的任务就如标题所示,搭建一个可以运行的博客,具备基本的登录功能,可以对自己的文章进行增删改查功能。目标定下,接下来开始行动。代码在此","text":"最近给自己定下一个任务,用rust写一个可以运行的项目,最终定下的任务就如标题所示,搭建一个可以运行的博客,具备基本的登录功能,可以对自己的文章进行增删改查功能。目标定下,接下来开始行动。代码在此 Actix_blog https://github.com/TigerInYourDream/actix_blog_example 选定框架 搭建博客自然不可能能徒手写一个,初步定下使用Rust的web框架actix(其实还有另一个方案Rocket)。工欲善其事必先利其器,先阅读actix_web的基本资料了解actix_web的使用方式。再快速阅读玩actix_web的文档后,发现了问题。因为本人没有web开发的相关经历,不理解web项目的组织方式,很多术语都看不懂,所以开发中的第一个难题就出现了:不理解web项目的组织方式,无法开始web项目的开发。既然基础薄弱无法开始,那么去了解web项目的组织方式就是当务之急。actix_web的文档本身可以说是有些过于简单,无法帮我达成这一目的。这个时候怎么办呢?多交流!和朋友的交流中得到一个信息,或许可以通过阅读 文档达到理解web项目的组织方式。(说明:本人并不会ruby)所以直接阅读rails文档。通过阅读rails文档,得到以下结论: web项目基本上是典型的mvc模式,v = view.是视图层。主要用于呈现界面。m = modle 是数据层,主要用户储存数据,是用db或者orm就在这一层,c是衔接v和m的控制层,web框架的主要作用就是充当c层。 把actix_web和rails的概念对应起来,可以这么说。controller作为控制层是为了粘合其他部分的。在代码中的api文件夹里的代码就是c层。handle函数则是具体处理对应逻辑的函数,相当于rails里面的Action。模板很好理解,就是相当于rails里面的erb文件。(也就是c)。router这个术语就是给不同的网络请求分配对应的handler(Action)函数的。至于中间件middleware,则是夹在用户请求和响应之间的功能,名副其实的中间,可以做到加载特定参数或者写log等一下功能。 到此,第一步选定框架,理解web术语的基本概念已经完成,可以动手了。以上结论并不重要,重要的是去阅读rails的文档,和看具体的代码,自己理解web的运作和组织方式。 选择Orm rust中的orm可选的不多,diesel最有名,那就它了。diesel(吐槽一下,rust世界框架的名字,柴油机???)的好处在于文档很齐全,而且我们在之前定下的任务就是增删改查,目标简单。Orm选定还牵扯db的选择,emmmm,官方指引实例用的postgreSQL,那就它了!至于如何安装postgreSQL,请参考上一篇文章,已经说的很清楚,就不在这里重复了。安装上postgreSQL之后,根据diesel文档,安装diesle-cli。按照文档一步一步来就可以知道我们diesel怎么使用。现在我们开始设计我们的数据表。充分理解我们的目标之后,我们大概需要一个,用户表,文章表,分类表等几类表,具体用了什么表可以去看代码。本身也不复杂,不过如果以后继续写web项目,这个过程也是必不可少的。这里面遇到一个问题。diesel链接数据库的时候,dotenv并不能识别 .env文件中的DATABASE_URL路径,我之后把里面的路径直接写进去才成功链接数据库。但是在所有的example中.env都是同样的写法。现在尚未知道原因。 开始正式使用actix_web 数据层的基础初步搭建好之后,开始引入actix_web。其实现在还是不能直接动手写acti_web。我们还是需要理清actix_web中的几个概念才可以开始。 提取器。提取器就是从handle函数中提取信息,主要提取的信息有路径,动态路径,form表单信息和其他存在body里面穿过来的信息。另外App状态也可以在handle函数中提取出来 当然以上的结论并不是一蹴而就的。需要阅读再回头阅读actix_web的文档。如果还是不理解,可以横向对比rocket的文档。个人认为rocket的文档,结构更加清晰,更能让人理解web的运行方式。其中life-cycle比较精彩,让人直观了解了rocket的运作过程,而且对handle函数的讲解更加清晰。我最后理解handler函数以及提取器就是反复阅读rocket文档的结果。现在开始启动actix服务器,加载app程序,加载router。编码的过程不再赘述,下面强调一下actix-web的架构或者说运作方式 actix_web启动一个服务器,绑定到指定的ip和端口,在服务器上装载或者叫运行一个(多个也可以)App。其中App实例可以加载router,用router来给不同的链接(不同的链接指不同的url和请求方式)分配不同的handler处理函数。handle处理函数中使用提取器获取诸多请求信息进行处理(处理部分主要是结合ORM),根据要求返回不同的结果。web程序中的结果就是渲染不同的页面或者在页面中进行跳转。 如何让diesel支持异步 这是个让人头痛的问题,一般来说到上一步基本的框架已经理清了,剩下的就是写代码。但是diesel不支持异步,我们如何完美结合web框架来使用它呢。在actix_web官方文档中就有这样一节。说实话,讲的不是很好。对于我来说,读了两边完全不理解是在干什么。不过好处在在提示我:快去使用Actor。那什么是Actor呢。幸运的是使用Actor模型太多了。比如erlang,AKKA和Elixir。简单来说Actor模型思路就是万物皆Actor。所有的Actor都是独立的,他们之间通过消息来交流,Actor维护一个队列(mail queue)来处理消息。好了,了解了actor的基本思路之后,继续看文档中的database一小节来明白actor到底怎么用。看懂了吗?我觉得是看不懂的,所以直接看github上的actix(不是actix_web)指引文档。所以该怎么用呢 actor既然要通过消息来交流,那我们就需要两个东西:消息(作为信息传递),Actor本体(作为发送和处理消息的载体)。所以,我们要做的很简单,包装一个message,然后包装一个actor.在actor里面处理消息即可。打开actor的源码你会发现一句话 Method is called for every message received by this Actor 说明啥呢?说明我们想的没错。根据例子就知道这个actor该怎么用了。 现在知道了actor怎么用了,我们开始直面问题 “如何让diesel支持异步”。还记得前面的提取器吗,提取器可以在handler里面提取什么?应该是路径,动态路径,状态,还有body里面的信息。别的几样都是传递过来的,那数据库信息该放哪里就有点眉目了?对!存在AppState里面。App.with_state(xxx)。好了,现在看actix_web的文档database一节就知道这一套该怎么用了。几个基本的问题搞清楚之后主要的障碍就不存在了。 使用Askma 不再强调了,看文档即可。之所以选Askma是我觉得Askma比另一个模板渲染看起来更简单。 回顾 最后总结整合思考过程:自顶向下,差缺补漏 首先了解web项目的组织方式,在看不懂actix_web项目的情况下去阅读文档更为清晰的rails,建立基础概念何为web项目,他的基础架构是什么,怎么组织。解决了这三个基本问题之后就可以初步开始了。接下来处理ORM部分,这一部分就是查找资料。接下来是web项目的的具体细节问题,何为controller,何为handler何为提取器,Appstate是什么,这一部分的理解是参考rocke的文档,横向对比得来的。最后是何为actor,幸运的是资料很多,很快能达成基本的理解。 最后列出需要查阅的文档 Actix_web https://actix.rs/docs/ Diesel http://diesel.rs/guides/getting-started/ Ruby on Rails https://ruby-china.github.io/rails-guides/getting_started.htm Rocket https://github.com/SergioBenitez/Rocket 其他材料可以自行查阅,另外就是英文文档比汉字的容易理解一些。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"Rust","slug":"Rust","permalink":"https://imzy.vip/tags/Rust/"},{"name":"actix_web","slug":"actix-web","permalink":"https://imzy.vip/tags/actix-web/"}]},{"title":"Mac上安装和使用PostgreSQL的方法","slug":"Mac上安装和使用PostgreSQL的方法","date":"2019-03-27T16:21:13.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/31755/","link":"","permalink":"https://imzy.vip/posts/31755/","excerpt":"最近需要使用actix-web来搭建一个web程序,这篇文章是关于搭建web程序的准备工作,","text":"最近需要使用actix-web来搭建一个web程序,这篇文章是关于搭建web程序的准备工作, 如何在Mac上安装PostgreSQL 因为本机上已经安装了homebrew 故而使用home brew brew install postgresql 安装的位置如下 /usr/local/var/postgres 安装成功后已经可以使用命令行 pg_ctl -V 来查看psql的版本,正确显示版本说明已经安装成功了。 在mac上安装PostgreSQL,需要开启psql的服务,仔细观察安装PostgreSQL的提示,brew已经提示你如何开启服务了。 brew services start postgresql 或者使用 if you don’t want/need a background service you can just run: pg_ctl -D /usr/local/var/postgres start 对应的使用如下命令来停止PostgreSQL的服务 brew services stop postgresql 也可以根据直接使用如下命令来查看brew启动的服务,不过属于brew的操作,和本文无关 brew services list 注意,很多文章都说需要在bash 或者zsh中添加环境变量,可能因为是版本的原因,截止我发文时间,是不需要添加envpath的。 使用 creatdb 创建出一个以当前系统用户名为数据库用户名的数据库 使用 psql 这时进入数据库控制台,相当于系统用户进入同名的数据库中,终端中会显示 xxxx = # 其中 xxxx 代表当前系统用户名 以下操作psql下执行 为当前数据库设置一个密码,默认是没有密码的 \\password , 按提示输入锁设置的密码就行,注意没有密码似乎不可以授权。 在此之前可以使用 \\du来查看当前的所有用户 创建一个数据库用户 注意以下下三条命令都需要; CREATE USER dbuser WITH PASSWORD 'password;' 创建成功后会出现 CREATE ROLE 为数据库用户建立一个数据库 这里创建的数据库是exampledb 数据库的拥有者是dbuser CREATE DATABASE exampledb OWNER dbuser; 成功后依然有提示 将exampledb数据库的所有权限都赋予dbuser,否则dbuser只能登录控制台,没有任何数据库操作权限。 GRANT ALL PRIVILEGES ON DATABASE exampledb to dbuser; 成功后也有提示。吐槽一下,这一点也不符合linux哲学,成功后什么也不发生,错误有提示才是真正的liux风格! \\q 退出 然后使用新用户名 密码 登录 psql -U dbuser -d exampledb -h 127.0.0.1 -p 5432 接下来可以尝试创建表格 插入数据等工作。之后为SQL操作。 除了以上的命令行方式,也可以使用官方提供的pgadmin工具,或者DataGrid 工具;这些不包含在本文中。","categories":[{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"技术","slug":"技术","permalink":"https://imzy.vip/tags/%E6%8A%80%E6%9C%AF/"},{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"https://imzy.vip/tags/PostgreSQL/"}]},{"title":"初见博客","slug":"初见博客","date":"2019-03-24T23:03:59.000Z","updated":"2024-08-12T16:01:47.534Z","comments":true,"path":"posts/39190/","link":"","permalink":"https://imzy.vip/posts/39190/","excerpt":"混沌初开,第一篇博客。之前在另一个GitHub账号上也创建了Hexo的Blog,现在新开了Blog账号,开启新的旅程。敬请期待","text":"混沌初开,第一篇博客。之前在另一个GitHub账号上也创建了Hexo的Blog,现在新开了Blog账号,开启新的旅程。敬请期待","categories":[{"name":"感悟","slug":"感悟","permalink":"https://imzy.vip/categories/%E6%84%9F%E6%82%9F/"}],"tags":[{"name":"感悟","slug":"感悟","permalink":"https://imzy.vip/tags/%E6%84%9F%E6%82%9F/"}]}],"categories":[{"name":"rust","slug":"rust","permalink":"https://imzy.vip/categories/rust/"},{"name":"技术笔记","slug":"技术笔记","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"},{"name":"天文学","slug":"天文学","permalink":"https://imzy.vip/categories/%E5%A4%A9%E6%96%87%E5%AD%A6/"},{"name":"天文","slug":"天文","permalink":"https://imzy.vip/categories/%E5%A4%A9%E6%96%87/"},{"name":"科普","slug":"科普","permalink":"https://imzy.vip/categories/%E7%A7%91%E6%99%AE/"},{"name":"技术文章","slug":"技术文章","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/"},{"name":"感悟","slug":"感悟","permalink":"https://imzy.vip/categories/%E6%84%9F%E6%82%9F/"},{"name":"算法","slug":"算法","permalink":"https://imzy.vip/categories/%E7%AE%97%E6%B3%95/"},{"name":"技术","slug":"技术","permalink":"https://imzy.vip/categories/%E6%8A%80%E6%9C%AF/"},{"name":"读书","slug":"读书","permalink":"https://imzy.vip/categories/%E8%AF%BB%E4%B9%A6/"}],"tags":[{"name":"c++","slug":"c","permalink":"https://imzy.vip/tags/c/"},{"name":"bingen","slug":"bingen","permalink":"https://imzy.vip/tags/bingen/"},{"name":"合约","slug":"合约","permalink":"https://imzy.vip/tags/%E5%90%88%E7%BA%A6/"},{"name":"solana","slug":"solana","permalink":"https://imzy.vip/tags/solana/"},{"name":"eve","slug":"eve","permalink":"https://imzy.vip/tags/eve/"},{"name":"-evm","slug":"evm","permalink":"https://imzy.vip/tags/evm/"},{"name":"rust","slug":"rust","permalink":"https://imzy.vip/tags/rust/"},{"name":"占星","slug":"占星","permalink":"https://imzy.vip/tags/%E5%8D%A0%E6%98%9F/"},{"name":"-rust -星座","slug":"rust-星座","permalink":"https://imzy.vip/tags/rust-%E6%98%9F%E5%BA%A7/"},{"name":"区块链","slug":"区块链","permalink":"https://imzy.vip/tags/%E5%8C%BA%E5%9D%97%E9%93%BE/"},{"name":"比特币","slug":"比特币","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"},{"name":"POW","slug":"POW","permalink":"https://imzy.vip/tags/POW/"},{"name":"substrate","slug":"substrate","permalink":"https://imzy.vip/tags/substrate/"},{"name":"数据库","slug":"数据库","permalink":"https://imzy.vip/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"},{"name":"多线程","slug":"多线程","permalink":"https://imzy.vip/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"},{"name":"jni","slug":"jni","permalink":"https://imzy.vip/tags/jni/"},{"name":"符号对照","slug":"符号对照","permalink":"https://imzy.vip/tags/%E7%AC%A6%E5%8F%B7%E5%AF%B9%E7%85%A7/"},{"name":"安卓","slug":"安卓","permalink":"https://imzy.vip/tags/%E5%AE%89%E5%8D%93/"},{"name":"Android","slug":"Android","permalink":"https://imzy.vip/tags/Android/"},{"name":"交叉编译","slug":"交叉编译","permalink":"https://imzy.vip/tags/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91/"},{"name":"SPV钱包","slug":"SPV钱包","permalink":"https://imzy.vip/tags/SPV%E9%92%B1%E5%8C%85/"},{"name":"系列文章","slug":"系列文章","permalink":"https://imzy.vip/tags/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0/"},{"name":"交易","slug":"交易","permalink":"https://imzy.vip/tags/%E4%BA%A4%E6%98%93/"},{"name":"笔记","slug":"笔记","permalink":"https://imzy.vip/tags/%E7%AC%94%E8%AE%B0/"},{"name":"问题经验","slug":"问题经验","permalink":"https://imzy.vip/tags/%E9%97%AE%E9%A2%98%E7%BB%8F%E9%AA%8C/"},{"name":"SPV节点","slug":"SPV节点","permalink":"https://imzy.vip/tags/SPV%E8%8A%82%E7%82%B9/"},{"name":"比特币网络协议","slug":"比特币网络协议","permalink":"https://imzy.vip/tags/%E6%AF%94%E7%89%B9%E5%B8%81%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/"},{"name":"思考","slug":"思考","permalink":"https://imzy.vip/tags/%E6%80%9D%E8%80%83/"},{"name":"Rust","slug":"Rust","permalink":"https://imzy.vip/tags/Rust/"},{"name":"数据溢出","slug":"数据溢出","permalink":"https://imzy.vip/tags/%E6%95%B0%E6%8D%AE%E6%BA%A2%E5%87%BA/"},{"name":"学习","slug":"学习","permalink":"https://imzy.vip/tags/%E5%AD%A6%E4%B9%A0/"},{"name":"复杂交易","slug":"复杂交易","permalink":"https://imzy.vip/tags/%E5%A4%8D%E6%9D%82%E4%BA%A4%E6%98%93/"},{"name":"fundrawtransaction","slug":"fundrawtransaction","permalink":"https://imzy.vip/tags/fundrawtransaction/"},{"name":"总结","slug":"总结","permalink":"https://imzy.vip/tags/%E6%80%BB%E7%BB%93/"},{"name":"RPC","slug":"RPC","permalink":"https://imzy.vip/tags/RPC/"},{"name":"设置","slug":"设置","permalink":"https://imzy.vip/tags/%E8%AE%BE%E7%BD%AE/"},{"name":"bitcoin","slug":"bitcoin","permalink":"https://imzy.vip/tags/bitcoin/"},{"name":"测试币","slug":"测试币","permalink":"https://imzy.vip/tags/%E6%B5%8B%E8%AF%95%E5%B8%81/"},{"name":"SPV","slug":"SPV","permalink":"https://imzy.vip/tags/SPV/"},{"name":"前端","slug":"前端","permalink":"https://imzy.vip/tags/%E5%89%8D%E7%AB%AF/"},{"name":"技术","slug":"技术","permalink":"https://imzy.vip/tags/%E6%8A%80%E6%9C%AF/"},{"name":"编程之道","slug":"编程之道","permalink":"https://imzy.vip/tags/%E7%BC%96%E7%A8%8B%E4%B9%8B%E9%81%93/"},{"name":"JavaScript","slug":"JavaScript","permalink":"https://imzy.vip/tags/JavaScript/"},{"name":"if() 和 ==","slug":"if-和","permalink":"https://imzy.vip/tags/if-%E5%92%8C/"},{"name":"遍历","slug":"遍历","permalink":"https://imzy.vip/tags/%E9%81%8D%E5%8E%86/"},{"name":"网络","slug":"网络","permalink":"https://imzy.vip/tags/%E7%BD%91%E7%BB%9C/"},{"name":"思维导图","slug":"思维导图","permalink":"https://imzy.vip/tags/%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE/"},{"name":"阅读","slug":"阅读","permalink":"https://imzy.vip/tags/%E9%98%85%E8%AF%BB/"},{"name":"actix_web","slug":"actix-web","permalink":"https://imzy.vip/tags/actix-web/"},{"name":"PostgreSQL","slug":"PostgreSQL","permalink":"https://imzy.vip/tags/PostgreSQL/"},{"name":"感悟","slug":"感悟","permalink":"https://imzy.vip/tags/%E6%84%9F%E6%82%9F/"}]}