Akawa

ETY001的博客

由于我正在搭建的测试网络属于私有链,在这两天的开发中,我发现使用 bitshares-ws 中的 Apis 进行初始化的时候,一直有下面截图中的问题

我的代码是官方文档示例中的

1
2
3
4
5
import {Apis} from 'bitsharesjs-ws';
const apiUrl = 'wss://api-testnet.61bts.com';
Apis.instance(apiUrl, true).init_promise.then((res) => {
console.log(res);
});

即返回值的第一个值一直是 undefined,目前还不确定这个会导致什么问题,不过有个 undefined 肯定是不对的。

去看了下 Apis.instance().init_promise 的代码,我摘了关键部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const newApis = () => ({
connect: (
cs,
connectTimeout,
optionalApis = { enableCrypto: false, enableOrders: false }
) => {
......
Apis.init_promise = Apis.ws_rpc
.login(rpc_user, rpc_password)
.then(() => {
//console.log("Connected to API node:", cs);
Apis._db = new GrapheneApi(Apis.ws_rpc, "database");
Apis._net = new GrapheneApi(Apis.ws_rpc, "network_broadcast");
Apis._hist = new GrapheneApi(Apis.ws_rpc, "history");
if (optionalApis.enableOrders)
Apis._orders = new GrapheneApi(Apis.ws_rpc, "orders");
if (optionalApis.enableCrypto)
Apis._crypt = new GrapheneApi(Apis.ws_rpc, "crypto");
var db_promise = Apis._db.init().then(() => {
//https://github.com/cryptonomex/graphene/wiki/chain-locked-tx
return Apis._db.exec("get_chain_id", []).then(_chain_id => {
Apis.chain_id = _chain_id;
return ChainConfig.setChainId(_chain_id);
//DEBUG console.log("chain_id1",this.chain_id)
});
});
......
let initPromises = [db_promise, Apis._net.init(), Apis._hist.init()];

if (optionalApis.enableOrders) initPromises.push(Apis._orders.init());
if (optionalApis.enableCrypto) initPromises.push(Apis._crypt.init());
return Promise.all(initPromises);
})
......
},
......
});

可以看到 Apis.init_promise 最终是返回了一个 Promise.all(),其中索引 0 的值是 db_promise

再看了一下,db_promise 的结果由 ChainConfig.setChainId() 来决定。

再找出 ChainConfig 的代码,这个文件代码不长,我就全部贴出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
var config = {
core_asset: "CORE",
address_prefix: "GPH",
expire_in_secs: 15,
expire_in_secs_proposal: 24 * 60 * 60,
review_in_secs_committee: 24 * 60 * 60,
networks: {
BitShares: {
core_asset: "BTS",
address_prefix: "BTS",
chain_id:
"4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8"
},
Muse: {
core_asset: "MUSE",
address_prefix: "MUSE",
chain_id:
"45ad2d3f9ef92a49b55c2227eb06123f613bb35dd08bd876f2aea21925a67a67"
},
Test: {
core_asset: "TEST",
address_prefix: "TEST",
chain_id:
"39f5e2ede1f8bc1a3a54a7914414e3779e33193f1f5693510e73cb7a87617447"
},
Obelisk: {
core_asset: "GOV",
address_prefix: "FEW",
chain_id:
"1cfde7c388b9e8ac06462d68aadbd966b58f88797637d9af805b4560b0e9661e"
}
},

/** Set a few properties for known chain IDs. */
setChainId: chain_id => {
let result = Object.entries(config.networks).find(
([network_name, network]) => {
if (network.chain_id === chain_id) {
config.network_name = network_name;

if (network.address_prefix) {
config.address_prefix = network.address_prefix;
}
return true;
}
}
);

if (result) return { network_name: result[0], network: result[1] };
else console.log("Unknown chain id (this may be a testnet)", chain_id);
},

reset: () => {
config.core_asset = "CORE";
config.address_prefix = "GPH";
config.expire_in_secs = 15;
config.expire_in_secs_proposal = 24 * 60 * 60;

console.log("Chain config reset");
},

setPrefix: (prefix = "GPH") => (config.address_prefix = prefix)
};

export default config;

可以看到在 ChainConfig 对象中有个 networks 的值,在进行 setChainId() 操作的时候,会在这个 networks 里搜索,找不到符合条件的结果,就会进入 else,也就是这个 setChainId() 会返回 undefined

好了找到了问题所在,那么我们只需要在 Apis.instance().init_promise 之前,先配置下 ChainConfig

但是 ChainConfiginit_promise 中,我们怎么配置呢?

结合上一篇文章 https://akawa.ink/2019/12/12/bitshares-ws-api-url-confuse.html ,我们知道使用 import 可以引入库的引用,那么我们在调用前,先 import ChainConfig 配置好就OK了。

最终代码如下:

1
2
3
4
5
6
7
8
9
10
import {Apis, ChainConfig} from 'bitsharesjs-ws';
const apiUrl = 'wss://api-testnet.61bts.com';
ChainConfig.networks['LiuyeTest'] = {
core_asset: 'TEST',
address_prefix: 'TEST',
chain_id: 'd5dfe0da7cda9426dc4761752d889d44401c5f04f76c17970e90437b02c038d4',
};
Apis.instance(apiUrl, true).init_promise.then((res) => {
console.log(res);
});

目前正在开发针对 Bitshares 的测试工具,一开始就让我遇到了疑惑,先来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {Apis} from "bitsharesjs-ws";
var {ChainStore} = require("bitsharesjs");

Apis.instance("wss://eu.nodes.bitshares.ws", true).init_promise.then((res) => {
console.log("connected to:", res[0].network);
ChainStore.init().then(() => {
ChainStore.subscribe(updateState);
});
});

let dynamicGlobal = null;
function updateState(object) {
dynamicGlobal = ChainStore.getObject("2.1.0");
console.log("ChainStore object update\n", dynamicGlobal ? dynamicGlobal.toJS() : dynamicGlobal);
}

上面这段代码是来自 bitshares-js 的官方文档的示例代码,其中我有两个疑惑,一个是 API 地址的设置,一个是 subscribe 是在什么时候调用的,ChainStore 到底该如何使用。

这篇文章是来写第一个疑惑的。

通过翻代码,可以看到在 ChainStore.init() 中也有调用 Apis.instance(),但是却没有方法指定 APIURL。于是我很好奇这个到底是怎么确定 ChainStore 在使用哪个节点呢?

去看 bitshares-ws 中关于 Apis.instance() 的代码,发现有其中一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Apis = null;
export const instance = (
cs = "ws://localhost:8090",
connect,
connectTimeout = 4000,
optionalApis,
closeCb
) => {
if (!Apis) {
Apis = newApis();
Apis.setRpcConnectionStatusCallback(statusCb);
}

if (Apis && connect) {
Apis.connect(cs, connectTimeout, optionalApis);
}
if (closeCb) Apis.closeCb = closeCb;
return Apis;
};

这里我们可以看到在 Apis.instance() 这个库代码里,有一个变量 Apis,对于我这种 js 半路出家的人来说,并不理解这个 Apis 到底是在哪个局部生效的。

也就是说,我在同一个项目的不同位置,import 同一个库的时候,库里的变量到底是指向两个内存地址,还是指向了同一个内存地址。按照目前 bitshares-js 给的示例代码来猜测,是指向了同一个内存地址。

于是我自己写了个简单的例子,来测试了一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- main.js --
import t from './test2';
import init from './test1';

t();
const anotherGVal = init();
console.log('another g val:', anotherGVal);

-- test1.js --
var g = null;
export const init = () => {
if (!g) {
console.log('not defined');
g = 1;
}
console.log('has defined');
return g;
}

-- test2.js --
import init from './test1';

export const t = () => {
const gVal = init();
console.log('gVal is:', gVal);
}

最终指向结果就是,只打印了一次 not defined,也就说明了,在我的测试代码中,第二次调用 test1.init() 的时候,变量 g 其实已经存在了。

这也就说明了 ChainStore.init() 中再次调用 Apis.instance() 的时候,由于之前已经调用过,所以 Apis 已经存在了。

再后来我又搜索了关于 javascriptimport 相关的文章,发现了这篇文章 https://zhuanlan.zhihu.com/p/33843378

** 总结下就是在同个项目下, import 同一个库只会执行一次,且返回结果是引用。 **

昨天重做了一个线上VPS的操作系统,结果忘记了备份 nginx 配置,
其中有一个配置还是比较棘手,这个服务是我的 Online Clipboard

这个工具最初后端服务是基于 Swoole 实现的 Websocket,后来
用户提了一个需求,希望在命令行下也能用,所以又在 Websocket
基础上增加了响应 HTTP 的功能,在代码里可以很清楚的看到这些。

然后最终的服务是依靠 Nginx 反代出来的。这里就有个问题,那就是
WebsocketHTTP 都在一个路径下面。也就是说下面的两个路径
一个可以创建 Websocket 连接,一个可以直接通过 HTTP 访问 (其中
usernamepassword 是参数)

1
2
wss://oc-server.to0l.cn/username/password
https://oc-server.to0l.cn/username/password

尴尬的事情就是,之前我怎么配置的忘记了。这次重做系统丢掉配置后,
重新配置怎么也配不出来了,最后搜索到一个方案,就是通过报文头来
区分是 Websocket 还是 HTTP,示例配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

upstream clip-server {
server 172.20.0.2:8080;
}

server {
listen 443 ssl;
server_name oc-server.to0l.cn;
ssl_certificate /etc/nginx/ssl/oc.to0l.cn.fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/oc.to0l.cn.key;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;

location / {
try_files /noexistfile @$http_upgrade;
}

location @websocket {
proxy_pass http://clip-server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}

location @ {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://clip-server;
}
}

思路就是解析 / 的时候,尝试访问一个不存在的文件,既然不存在,
那么势必要内部访问 location @$http_upgrade

当来访访问是 Websocket 请求的时候, $http_upgrade 的值是
websocket,而来访访问是 HTTP 请求的时候,则是空值。

这样就把两个不同报文头的请求转发到两个不同的 location 中去了。
这样之前尴尬的问题就解决了。

配置好后,通过 curl https://oc-server.to0l.cn/public/public -d "content=test"
测试提交数据到剪切板,成功!

通过 curl https://oc-server.to0l.cn/public/public 访问最后
一条剪切板数据,成功!

浏览器访问 https://oc.to0l.cn,分别输入剪切板名和密码后,可以
看到之前发的 test 信息,成功!

前因

有时候想搞点事情的时候,最终都是由于 BTS 目前的公共测试网络里你要用代币只能找别人要。
我是很不喜欢麻烦别人的,所以就放弃想搞点东西的想法了。

很好奇,BTS 作为一个好几年的老项目了,居然没有一个类似 ETH 那样的可以自行申请代币的测试网络,
真是神奇!

所以我打算自己部署一整套类似 ETH 那边 kovan 一样的测试网络。
测试网络每个季度重置数据,Dapp 开发人员可以通过机器人申请代币。

同时,部署方案我也会公开出来,如果你不想用我搭建的服务,也可以自建。

这第一篇是讲节点部署的,在我完成发币机器人和水龙头后,会再发布第二篇,介绍机器人和水龙头部署方法。

准备工作

由于考虑到部署尽量傻瓜化,所以我选择用 Docker 来作为部署底层支持。

这样,我们能够屏蔽掉很多环节,只需要很少量的工作,就可以完成目标。

开始部署

1.拉取测试网镜像

目前我已经把测试网的程序封装好了,如果你希望自己封装,可以参考的 Dockerfile 文件,
文件地址是:https://github.com/ety001/dockerfile/blob/master/bitshares-core-builder/Dockerfile.test

如果你想直接用现成的,那么直接执行下面的命令拉取我的镜像即可

1
docker pull ety001/bts-core-testnet:latest

latest 版本指向最新的测试网程序,如果你想用其他的版本,可以去这里看下我之前编译的版本 https://hub.docker.com/r/ety001/bts-core-testnet/tags

2.下载 docker-compose.yml 文件

1
wget https://raw.githubusercontent.com/ety001/dockerfile/master/btfdd/docker-compose.yml

3.创建数据目录

docker-compose.yml 文件的同目录下,创建数据目录,用于存放区块数据。

1
mkdir bts_data

4.创建 my-genesis.json 文件

bts_data 目录下,创建 genesis 目录,
并创建 my-genesis.json 文件用于配置创世块。

1
2
mkdir bts_data/genesis
touch bts_data/genesis/my-genesis.json

my-genesis.json 文件可以参考模板配置文件 genesis-dev.json,地址在这里:https://raw.githubusercontent.com/bitshares/bitshares-core/master/libraries/egenesis/genesis-dev.json

接下来我们需要修改 my-genesis.json 文件,以上面提到的 genesis-dev.json 模板为例。

我们需要修改默认11个见证人的 owner_keyactive_keyblock_signing_key 这三种值。

另外里面还有个 nathan 的用户,我们一并修改这个用户的 owner_keyactive_key

当然你也可以把 nathan 这个名字换成自己的,比如 ety001。这个账号,我们之后会拿来给机器人用。

这样这里就需要很多组公私钥对,我们可以使用下面的命令即可快速生成

1
2
3
4
5
docker run \
-it \
--rm \
ety001/bts-core:5.0.0-tools \
/app/get_dev_key TEST seed1 seed2 seed3 seed4

get_dev_key 是 BTS 自带的生成公私钥对的小工具,使用语法是:
第一个参数是公钥的前置,之后的参数都是生成私钥时所需要的 seed

上面的示例中,可以生成四组公私钥对,并且公钥是以 TEST 开头。

这里需要注意,测试网公钥必须要以 TEST 开头。

除了修改上面提到的 owner_keyactive_keyblock_signing_key 之外,
还需要修改 initial_balances 中的 ownerasset_symbol

  • owner 使用的是 address 而不是 public_key
  • asset_symbol 设置为 TEST
  • initial_balances 申领需要在节点启动后,在 cli 里使用 import_balance ety001 [5JLxxx] true 命令获取,其中 5JLxxxety001owner 权限的私钥。

5.创建 config.json 文件

接下需要启动一次容器,以创建 config.json 文件

1
docker-compose up

执行后,由于还没有配置见证人,所以会一直卡住。这个时候,我们 ctrl + c 结束,
bts_data 目录下就能看到 config.json 文件了。

6.配置见证人

我们需要把我们的11个见证人配置进 config.json 里。

我们把 config.json 里原有的 private-key 删掉,加入新的配置,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
witness-id = "1.6.1"
witness-id = "1.6.2"
witness-id = "1.6.3"
witness-id = "1.6.4"
witness-id = "1.6.5"
witness-id = "1.6.6"
witness-id = "1.6.7"
witness-id = "1.6.8"
witness-id = "1.6.9"
witness-id = "1.6.10"
witness-id = "1.6.11"

private-key = ["TEST7HSRc2ifS2Xzr98t1xQgdPWrM6zChe8gTAycKDYTCJvKJqVyJC", "5JQ7Uo6f8WmqnzoYcE5AkuSLFyUEQWP5uLNphykFnJCmpMXKa4D"]
private-key = ["TEST6hboHaJmrdSNdawxrjpHtM7dm5cbd16rBegrswer3yBfHth9Zo", "5JkW1yf6MwTGaP9CjimugZMRjgPuJMm1Pdmp785NndtXmWAfUe6"]
private-key = ["TEST8WbrXZn9v334bv5v4PKnDG36QZfkvt6qsK46knQQpxgA6RnHhV", "5KNBtnDp3yEu1RZchANYoZsTXCwQVvMQb42bbwjNtq3Woq7nRES"]
private-key = ["TEST8k9DRh9KrTjAYZFgFXwznAWkdD6Ke48URYWjgeit8jA5V1vnnU", "5JaFdWJvf9Vu1Bz98P1g4PjJGEUDquGzZ3vFtWXzuHZeLNiaTpF"]
private-key = ["TEST8CzmYt2sJVdRCpdx51N7YXjBFhyj17DS7oaiQ6MjLa8oHyFkUX", "5KgtUPZXF4AxQSqXVSqweRb8ixFfdev41c1jAszZ8PTSHowcXyc"]
private-key = ["TEST6PU8rAukkA8ynMqiBVxYDTsoYz8TFSZDZ68Hct9LVmhaYc6AbT", "5K9Fxnc9RvDS1CWUhrd89ap8zJ8hEggibYvahvAJKexSrTeZS46"]
private-key = ["TEST8FMi4q6A8hELQRJ5zrdwuW7tkjKABSP4DRuRVkVA3rp6s4HRZy", "5KKpUNdUMqEwT8hBhwc9p9x5JxeRxHgwAeTCPt3BjL1Yh7ASoU6"]
private-key = ["TEST8XEJor1k1f9jTd9GiS4VycmmaL6WTtj4NpKrz3BMPhW9w6pxw9", "5KKmySfMqXeeutsfYsbD8AonsxVm1cbC33nCZEvVbUsnc7KEshD"]
private-key = ["TEST7NLQf5P2csPdmfSbMftcBsfvKfY96RMHrpZtcrQHG454khzALY", "5Ji1FVJZDTnTFJ1dSGo7EGqroLCi9kuZMYqgvphYcSvKCWkEfZH"]
private-key = ["TEST6Gvr1QZdPQyFWnZrPsgUASTFUsfmZmrAWZKQjX1TL5yQs5GMro", "5KErQxNxaQJXHaeWGLonW23RSc6zqZuBUE9YCZASEyPG5nBjnkG"]
private-key = ["TEST6ujzsVvmKr1ASSDSukjC9EtobjMM1uVTWtYFPzJ5TvaZjCRGYz", "5JT6QzZnV14jCuyxqPk6BjPDFAvhYBfjfXkCgk5Mr5neA92voyA"]
  • 其中 private-key 要与 my-genesis.json 中的 block_signing_key 一致。
  • 注意 checkpoint 设置为 []

7.启动

至此,所有的配置都已完成,执行下面的命令启动即可

1
docker-compose up -d

结语

部署的主要工作就是配置 my-genesis.jsonconfig.json

如果有疑问,请到这里提 issue: https://github.com/ety001/bitshares-testnet-for-dapp-developers/issues

参考文档

感谢

@abit @Necklace

忙活了两周时间终于完工了第一个三角套利机器人。

什么是三角套利?

三角套利就是在交易市场中,你找到多个标的之间的差价,然后一通买卖盈利。

举个例子来说就是你手里有1美元。现在市场价格是 1 美元可以买 6.8 人民币,1人民币可以买 0.15 欧元,1 欧元可以买 1.1 美元。那么你按照这个顺序买卖下来,手里的 1 美元最后会变成 1.122 美元。这就是三角套利。

三角套利应该是量化交易里面最简单的策略了吧。这也算是我开始量化交易之路的开端吧。

这次是在 Bitshares 这个分布式交易所里进行的。Bitshares 里进行三角套利的最大好处就是无风险。

要知道三角套利最大的风险就是在搬砖的路上卡单。比如你 1 美元购买完 6.8 人民币后,这时 人民币到欧元的价格发生变化,或者你手里的 6.8 人民币按照 0.15 的价格无法完全成交,这就导致你的钱卡在了路上,很可能最后就是亏损。

而 Bitshares 的好处是,你可以一次性把整个链条上的单全部下单,只要一个单不成交,就都不成交,且没有任何手续费。

所以 Bitshares 在技术层面讲,是非常适合三角套利的。不过就是市场深度和交易活跃度不很乐观。于是,需要除了写下单的程序外,还需要写程序找到市场交易量不错的交易链条。

这两周最大的感慨就是,很简单的东西,自己抓瞎写了将近两周。感觉这两周智商被按在地上反复的摩擦的节奏,果然是外包做多了会变傻。还是得多读书啊。

Bitshares Python库的文档并没有写如果在一个tx中下多个单的方法,经过看源码,找到了方法。
现总结下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from bitshares import BitShares
from bitshares.market import Market
from bitshares.asset import Asset
from bitshares.account import Account
from bitshares.utils import formatTimeFromNow
from bitsharesbase import transactions, memo, account, operations, objects

API_URL = 'ws://127.0.0.1:8090/ws'
PRIV_KEY = '这是账号私钥'
ACCOUNT_NAME = 'ety001'

# 创建连接
bitshares = BitShares(API_URL, keys=PRIV_KEY)
# 创建账号实体
importAccount = Account(ACCOUNT_NAME, blockchain_instance=bitshares)

# 拿到 orderbook
market = Market('BTS:CNY', blockchain_instance=bitshares)
orderbook = market.orderbook(1)
sellAssetId = orderbook['bids'][0]['quote']['asset']['id']
receiveAssetId = orderbook['bids'][0]['base']['asset']['id']
amountToSell = 3 * 10 ** orderbook['bids'][0]['quote']['asset']['precision']
receiveAmount = 5 * 10 ** orderbook['bids'][0]['base']['asset']['precision']

# 创建订单
op1 = operations.Limit_order_create(
**{
"fee": {"amount": 0, "asset_id": "1.3.0"},
"seller": importAccount['id'],
"amount_to_sell": {
"amount": int(round(amountToSell)),
"asset_id": sellAssetId,
},
"min_to_receive": {
'amount': int(round(receiveAmount)),
"asset_id": receiveAssetId,
},
"expiration": formatTimeFromNow(60 * 60 * 24 * 7),
"fill_or_kill": True,
"extensions": [],
}
)
op2 = operations.Limit_order_create(
**{
"fee": {"amount": 0, "asset_id": "1.3.0"},
"seller": importAccount['id'],
"amount_to_sell": {
"amount": int(round(amountToSell)),
"asset_id": sellAssetId,
},
"min_to_receive": {
'amount': int(round(receiveAmount)),
"asset_id": receiveAssetId,
},
"expiration": formatTimeFromNow(60 * 60 * 24 * 7),
"fill_or_kill": True,
"extensions": [],
}
)
opList = []
opList.append(op1)
opList.append(op2)

# 下单
bitshares.finalizeOp(opList, ACCOUNT_NAME, 'active')

说明:

  1. 需要连接获取数据的方法,请使用参数 blockchain_instance 把你自己创建的对象传入,
    否则连接的 API 并不是你指定的。默认连接库作者的 API
  2. 买入卖出数量需要转换成整数,即乘以 precision

最近在开发 Bitshares 内盘交易机器人,使用的 pybitshares 库,文档写的很基础,真是苦了我了。

这不就遇到了下单时,给我说 UnhandledRPCError,然后就没有然后了。

WTF!这特么让我怎么调试?你代码不知道怎么处理,你倒是把错误信息打出来啊。

我尝试过了 pdb 工具,逐步调试,也没有拿到信息,主要是不熟悉调试工具。最后打算直接上 wireshark 抓包了。

不过由于公开节点都是 http 的,用 wireshark 抓包也没法看,都是加密的。

于是我只能把我远端的节点服务器上的 8090 端口映射到我本地,这样我就可以直接使用 http 协议了。

但是还有个问题,就是我的开发环境在 Docker 容器里,而 8090 是映射在宿主机上,
wireshark 抓包没有抓到(后来想了下,应该要监听错了网卡,应该监听 Docker 的那块虚拟网卡才对)。

抓不到包咋办?

搜索了下,发现 Linux 大多数发行版会带着一个叫做 nsenter 的工具。

这个工具可以以其他程序的名字空间运行某个程序。而 Docker 容器在宿主机上其实就是一个进程,
那么我们就可以用这个工具把容器的名字空间搞出来,再运行 wireshark,那就相当于是在容器里
直接运行 wireshark

所以,我们需要先看下当前开发用的 Docker 容器的进程ID

1
docker ps

找到进程ID是 4030,再执行下面的命令完成映射,

1
# nsenter -n -t 4030

这时候,再执行

1
# wireshark

这时就能在 wireshark 里看到跟宿主机不一样的网卡列表,这就成功了。

启动抓包,重新执行我的程序,终于拿到了错误信息!!

家里的服务器重做了系统后,bitshares节点我也计划重新换一种部署模式。

由于家里的网络是动态公网IP,联通会不定时把客户端踢下线,让其重新拨号,
这就导致 IP 会不定时变动。尽管做了 DDNS 脚本,每分钟去监测变动并修改,
但是架不住 DNS 的生效时间和脚本出现意外不可用。

所以新的部署方案,我使用 frp 来让我家里的服务器主动去把本地端口映射到
远程公网服务器上(目前是阿里云香港轻量服务器)。这样就能解决 IP 变动
带来的潜在不稳定因素了。

另外一个好处就是,这样相当于是在节点前加了一层,保证了自己真实 IP 不暴露。
即使有 DDoS 过来,也是前面的阿里云的节点挂掉。

除了部署方案调整了以外,节点的 URL 地址也发生了变动。

鉴于之前 liuye.tech 这个域名是注册给公司用的,现在公司已经注销了,
所以这个域名就不再继续使用了。

新注册了 61bts.com 专门用来放 BTS 相关的东西。

新的 API 地址是:

wss://api.61bts.com

之前不知道怎么想的,在一台物理机上通过 docker 来搭建两个 Node 的 Elasticsearch 集群。

由于 ES 默认分片要有一个备份,所以导致我的硬盘用量很大,关键是在同一台物理机上搞两个 Node 没有什么意义。

所以今天我又换回了单 Node 模式。

新的 Dockerfile 我放在了我的库里,https://github.com/ety001/dockerfile/tree/master/bts-es
那个 docker-compose.yml.single 就是了。

切换回单节点后,集群的健康状态里就会显示各个索引都是 yellow。原因就是刚才说的,ES 默认会
保持一个备份分片,而单节点后,没法分发备份分片到其他节点,所以就会报不健康。

我们可以通过执行下面的命令,来让分片默认0备份

1
2
3
4
5
6
7
8
$ curl -u elastic:123456 -XPUT "http://127.0.0.1:9200/_template/default_template" -H 'Content-Type: application/json' -d'
{
"index_patterns": ["*"],
"settings": {
"number_of_replicas": 0
}
}
'

通过设置默认模板,再创建的新的分片就不再需要有备份了。

PS:我搭建的国内 Bitshares ES 节点也上线了,目前还在数据同步中

1
2
3
https://es.61bts.com
用户名: bts
密码: btsbts

昨天新买的四条服务器内存到了,于是早上去安装一下。

很不幸的是,关机安装完内存条后,系统起不来。第一反应是内存没有插好。
拔下所有新装的内存,先试试能不能开机,结果不能。

系统一直卡在一个光标闪烁的状态。

由于一直就很想重做这个服务器的系统,当时装的时候,忘记做LVM了,
所以这次故障也懒得修了,直接开始重做系统。

花了2个小时,把基础系统做完,重启了几遍看看有没有什么问题,一切正常。

看到内存插满主板达到256G顶值后,心里美滋滋,心想这下可以大开杀戒了。

结果下午回到家,没安装几个服务,主机就挂了。

由于我的服务器电源连接在米家的电源开关上,所以我进行了远程重新断电上电操作。
通过米家可以看到机器自动启动成功了,但是一直连不上,怕是系统又进不去了。

折腾了半天没有成功后,不得不再回去看看是什么情况。

重新接上显示器后,发现跟早上的情况一个样,目测应该是硬盘有问题。打开 BIOS 看了下,果然

一共三块硬盘,只识别出来了两块,最关键的那块带这 grub 引导的硬盘没有识别出来。

考虑到新系统上了 LVM ,我这块没有识别出来的硬盘(金士顿)和另外一块东芝的做成了一个逻辑组,
所以在考虑能不能把金士顿这块从逻辑组里踢出去。

但是我重启引导进U盘里的系统后,发现并没有金士顿那个硬盘

有点慌!既然系统间歇性能启动起来,那看来硬盘坏的不严重。
估计重启几次,肯定会有一次能挂上。在重启了三四次后,终于检测出来了。

好,既然监测出来了,让我先理顺一下目前的情况。

1.当前是金士顿(/dev/sdb1)和东芝(/dev/sdc1)做了一个逻辑组 /dev/vssd

2.然后在 /dev/vssd 这个逻辑组上划分了两个逻辑卷 /dev/vssd/vboot/dev/vssd/vmain

3.系统有两个分区,/ 挂在 /dev/vssd/vmain 上,/boot 挂在 /dev/vssd/vboot 上。
尝试了挂载 //boot ,发现 / 挂载成功,/boot 挂载失败,提示磁盘有错误。
系统目前不超过 7G 大。

情况理顺完,我们的思路也就有了。

  1. /dev/vssd/vmain 缩小到 /dev/sdc1 的大小以内,
  2. /dev/vssd 中把 /dev/vssd/vboot 删除,
  3. /dev/sdb1 这个 pv/dev/vssd 逻辑组中删除,这个时候,逻辑组里就没有金士顿硬盘了,
  4. 在逻辑组 /dev/vssd 中再新建一个 /dev/vssd/vboot 逻辑卷,
  5. 重新调整 /dev/vssd/vmain 的大小,让其把剩余所有的空间占有,
  6. 重新挂在 /dev/vssd/vmain/dev/vssd/vboot,在 /dev/vssd/vboot 上安装内核,
  7. 重新生成 fstab
  8. 重新安装 Grub/dev/sdc 上,并生成新的 grub.cfg 存储到 /dev/vssd/vboot 上。

开始实施。

1.缩小 /dev/vssd/vmain,需要先监测才能缩小

1
w2fsck -f /dev/vssd/vmain

执行缩小,由于我的东芝是400多G,那么我只要缩小到400G内就好了,懒得计算,取了个整 300G

1
2
3
4
# 先缩小文件系统
resize2fs /dev/vssd/vmain 300G
# 再缩小逻辑卷
lvreduce -L 300G /dev/vssd/vmain

三步操作如下图

2.从 /dev/vssd 中把 /dev/vssd/vboot 删除,

1
lvremove /dev/vssd/vboot

3.从逻辑组中移除金士顿硬盘

1
vgreduce vssd /dev/sdb1

操作完可以看到逻辑组(VG)里只有一个 PV 了,并且 VG 的容量跟东芝的硬盘一样大了

4.新建 /dev/vssd/vboot

1
lvcreate -L 200M vssd -n vboot

5.扩容 /dev/vssd/vmain

1
2
# 注意这里参数是小写L
lvextend -l +100%FREE /dev/vssd/vmain

6.格式化 /dev/vssd/vboot 后,重新挂载

1
2
mkfs.ext2 /dev/vssd/vboot
mount /dev/vssd/vboot /mnt/boot

重新安装内核

1
pacstrap /mnt linux linux-firmware

7.生成新的 fstab

由于 /dev/vssd/vboot 是新创建的,所以原来分区的 UUID 也就不存在了,因此需要更新 fstab
否则启动的时候会找不到分区。(通过截图可以看到原来的 UUID

1
genfstab -U /mnt > /mnt/etc/fstab

8.重新安装 Grub ,生成新的 grub.cfg

1
2
3
4
5
6
# 进入chroot
arch-chroot /mnt
# 安装 grub 到 /dev/sdc
grub-install --target=i386-pc /dev/sdc
# 生成新的 grub.cfg
grub-mkconfig -o /boot/grub/grub.cfg

9.所有操作结束后,退出 chroot,卸载所有已挂载分区,重启。

完美启动!!!!

0%