CI_Knight

且行善举,莫问前程。

0%

安装flow-cli

安装 flow-cli,MacOS 需要下载最新版本的二进制文件,brew 只能安装旧版本。具体安装方法见文档 Install the Flow CLI,该文档使用版本是 v0.25.0。

生成key-pair

1
2
3
4
5
$ flow keys generate --sig-algo=ECDSA_secp256k1

Store private key safely and don't share with anyone!
Private Key 11e67f5360e61d7d92eb73e1dc48ffbd71c55c8bdf727520d11ddfa6db63554d
Public Key cd23c71391d33c23c867319258ad6cd082b0066e564e23d3fda3c4e5b9ec841eaaa07d2189965130445a4edda244720628334f6a879d80220b573d9914edcbe2

创建testnet flow地址

访问页面:Flow Testnet Faucet ,创建地址。填入生成的 public key,选择ECDSA_secp256k1签名算法和SHA3_256(和 ETH 哈希算法相同)哈希算法。

创建成功后得到地址:0x25470ac6e0585a62,也可以跳过该步骤,直接使用生成好的地址。

初始化flow配置文件

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
$ mkdir flow-example; cd flow-example
$ flow init --service-priv-key 11e67f5360e61d7d92eb73e1dc48ffbd71c55c8bdf727520d11ddfa6db63554d --service-sig-algo=ECDSA_secp256k1 --service-hash-algo=SHA3_256

Configuration initialized
Service account: 0xf8d6e0586b0a20c7

Start emulator by running: 'flow emulator'
Reset configuration using: 'flow init --reset'

$ cat flow.json
{
"emulators": {
"default": {
"port": 3569,
"serviceAccount": "emulator-account"
}
},
"contracts": {},
"networks": {
"emulator": "127.0.0.1:3569",
"mainnet": "access.mainnet.nodes.onflow.org:9000",
"testnet": "access.devnet.nodes.onflow.org:9000"
},
"accounts": {
"emulator-account": {
"address": "f8d6e0586b0a20c7",
"key": {
"type": "hex",
"index": 0,
"signatureAlgorithm": "ECDSA_secp256k1",
"hashAlgorithm": "SHA3_256",
"privateKey": "11e67f5360e61d7d92eb73e1dc48ffbd71c55c8bdf727520d11ddfa6db63554d"
}
}
},
"deployments": {}
}

我们将配置里的地址 f8d6e0586b0a20c7 修改为 25470ac6e0585a62。

创建交易并签名广播

在构造 flow 的交易时,我们需要先使用 Cadence 语言编写执行脚本,代码如下:

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
$ cat > tx.cdc <<EOF
import FungibleToken from 0x9a0766d93b6608b7
import FlowToken from 0x7e60df042a9c0868

transaction(amount: UFix64, to: Address) {

// The Vault resource that holds the tokens that are being transferred
let sentVault: @FungibleToken.Vault

prepare(signer: AuthAccount) {

// Get a reference to the signer's stored vault
let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)
?? panic("Could not borrow reference to the owner's Vault!")

// Withdraw tokens from the signer's stored vault
self.sentVault <- vaultRef.withdraw(amount: amount)
}

execute {

// Get a reference to the recipient's Receiver
let receiverRef = getAccount(to)
.getCapability(/public/flowTokenReceiver)
.borrow<&{FungibleToken.Receiver}>()
?? panic("Could not borrow receiver reference to the recipient's Vault")

// Deposit the withdrawn tokens in the recipient's receiver
receiverRef.deposit(from: <-self.sentVault)
}
}
EOF

在 testnet 网络上,FlowToken 的合约地址是 0x7e60df042a9c0868。如果使用主网或者是 emulator,需要替换成对应的合约地址,具体查询:Flow Core Contracts

我们 cdc 代码中定义了 transaction 方法,并且包含两个参数 amount 和 to,我们构造交易时,也要传入对应参数。

我们使用 flow-cli 来构造交易,可以将 flow 发送到 0x72aeca95731d54e5(必须是一个真实创建的地址)。

1
2
3
4
5
6
7
8
9
10
$ flow transactions build ./tx.cdc \
--authorizer emulator-account \
--proposer emulator-account \
--payer emulator-account \
--args-json '[{"type": "UFix64", "value": "1.0"}, {"type": "Address", "value": "0x72aeca95731d54e5"}]' \
--network testnet \
--filter payload \
--save built.rlp

result saved to: built.rlp

我们只保存 payload,我们的接下来对 payload 进行签名,生成签名后的哈希。

1
2
3
4
$ flow transactions sign ./built.rlp --signer emulator-account \
--filter payload --save signed.rlp

result saved to: signed.rlp

广播签名后的交易。

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
$ flow transactions send-signed --network testnet ./signed.rlp

Sending transaction with ID: 8a979bd7d0278bada8b9cf57611608a5ba258090ecdddaaa83e2e5d04fe53af7

Status ✅ SEALED
ID 8a979bd7d0278bada8b9cf57611608a5ba258090ecdddaaa83e2e5d04fe53af7
Payer 25470ac6e0585a62
Authorizers [25470ac6e0585a62]

Proposal Key:
Address 25470ac6e0585a62
Index 0
Sequence 0

No Payload Signatures

Envelope Signature 0: 25470ac6e0585a62
Signatures (minimized, use --include signatures)

Events:
Index 0
Type A.7e60df042a9c0868.FlowToken.TokensWithdrawn
Tx ID 8a979bd7d0278bada8b9cf57611608a5ba258090ecdddaaa83e2e5d04fe53af7
Values
- amount (UFix64): 1.00000000
- from (Address?): 0x25470ac6e0585a62

Index 1
Type A.7e60df042a9c0868.FlowToken.TokensDeposited
Tx ID 8a979bd7d0278bada8b9cf57611608a5ba258090ecdddaaa83e2e5d04fe53af7
Values
- amount (UFix64): 1.00000000
- to (Address?): 0x72aeca95731d54e5

Index 2
Type A.7e60df042a9c0868.FlowToken.TokensWithdrawn
Tx ID 8a979bd7d0278bada8b9cf57611608a5ba258090ecdddaaa83e2e5d04fe53af7
Values
- amount (UFix64): 0.00010000
- from (Address?): 0x25470ac6e0585a62

Index 3
Type A.7e60df042a9c0868.FlowToken.TokensDeposited
Tx ID 8a979bd7d0278bada8b9cf57611608a5ba258090ecdddaaa83e2e5d04fe53af7
Values
- amount (UFix64): 0.00010000
- to (Address?): 0x912d5440f7e3769e

Index 4
Type A.912d5440f7e3769e.FlowFees.TokensDeposited
Tx ID 8a979bd7d0278bada8b9cf57611608a5ba258090ecdddaaa83e2e5d04fe53af7
Values
- amount (UFix64): 0.00010000


Code (hidden, use --include code)

Payload (hidden, use --include payload)

广播后如果 Status 为 SEALED 则发送成功。

我的博客一直是通过netlify来构建部署的,有一次升级hexo后,在netlify构建就失败了,并且一直没有解决这个问题。主要是我本地并没有失败过,所以我就考虑使用新的平台了。

vercel

现在已经切换到了vercel,部署更加简单,配置也更方便,也没有遇到过netlify构建异常。我怀疑是netlify的node版本过低,是8.x的版本。vercel能识别项目使用的框架,非常友好。

ipfs+ipns

去中心化是非常好的选择,如果使用的是ipfs,博客要使用相对路径,修改hexo配置中的relative_link就可以实现。当然也可以配置上ipns,ipns是基于dnslink实现,配置一下dnslink的txt记录即可。当你浏览器装了ipfs-companion的插件后,访问你的博客域名,就会自动重定向到ipfs的资源了。除了ipfs.io,现在cloudflare和infura也提供了ipfs的gateway,当然你也可以下载一个ipfs的软件,运行在本地来充当gateway。

后续更新

2021四月我将博客迁移至cloudflare的pages服务上,这是cloudflare刚上不久的服务,目前还是beta阶段,和vercel的配置是一样的,非常简单,vercel比较麻烦的地方是,需要通过命令行设置一下主域名,这样每次自动构建的页面才会部署在自定义域名上。

当然也可以安装ipfs的浏览器插件,直接访问我的博客,ipfs的gateway推荐使用cloudflare-ipfs.com。

最新加速方案

我讲pyenv-proxy部署在了Cloudflare上,json数据存储在了Cloudflare KV,用了Cloudflare workers 作为Web服务,不依靠我个人的服务器,保证服务的稳定。

新的配置如下:

1
export PYTHON_BUILD_MIRROR_URL="https://pyenv.ibeats.top"

workers代码请查看项:pyenv-proxy

参考文章:如何加速Pyenv

如果做网站加速,CDN是有很多种选择的,如果只做国内业务可以选择任意一家CDN都没问题,国外一般选择CloudFront和Cloudflare。Cloudflare在我看来,使用非常方便,傻瓜都可以操作,基本都是一键设置,不过功能相对于CloudFront少很多,毕竟CloudFront是AWS的一个服务。CloudFront优势在于可以和AWS的其他服务相结合,实现特定的需求。所以我更倾向于使用CloudFront,如果是个人静态博客之类的直接使用Cloudflare足矣。

这两个服务,国外加速效果都非常好,国内访问高峰期,有时会慢到让人无法接受,如果公司使用还是要对国内访问进行加速的,国内加速我更推荐使用CloudFront,虽说Cloudflare有很多黑科技,比如argo之类加速功能,但是比起CloudFront逊色不少。

由于国内特色的网络环境,想要解析到国内的服务器,一定要做网站备案,这就导致Cloudflare和CloudFront在国内并没有加速节点。不过Cloudflare有Partner曲线救国的方式,设置起来就比较麻烦了,而且最终还是逃不过备案。

我最终选择的方案是GeoDNS区域解析,因为域名的NS在Cloudflare托管,所以我优先选择使用Cloudflare的Load Balance,可以根据区域进行自定义解析,从使用体验上讲,该服务收费而且中国IP识别准确率也很低,划分区域相对简单,是根据大陆板块划分,没有根据国家划分区域,所以只好放弃Cloudflare。在一番对比后,国内的DNSPod,AliyunDNS准确率都很高,最后我还是选择了AWS的route53服务。

服务器可以考虑使用Alibaba Cloud的OSS服务,香港的OSS对于国内访问速度还是非常快的,而且比较稳定。将中国的解析执行OSS就可以了。当然这只是前端页面加速,如果是其他业务,也可以使用HAProxy或者Nginx做转发,就可以实现更多特殊的需求了。

如果写同步的程序,我都是使用 requests 发送 http 请求,异步程序,我更倾向于使用 aiohttp,这两个都是非常优秀的工具包,但是写异步代码,如果掌握不精很容易坑了自己。接下来我会讲述一下我在使用中遇到的一个问题。

版本和软件环境

1
2
3
System: MacOS 10.15
Python: 3.7.6
aiohttp: 3.5.4

aiohttp client 抛了异常

下面是简化过的代码。

http.py
1
2
3
4
5
6
7
8
9
10
11
12
import aiohttp

class Http:
def __init__(self):
connector = aiohttp.TCPConnector()
self.session = aiohttp.ClientSession(connector=connector)

async def get(self, url):
async with self.session.get(url) as r:
return await r.text()

http = Http()
main.py
1
2
3
4
5
6
7
8
9
from base import b

async def main():
r = await b.get("https://blog.ibeats.top/robots.txt")
print(r)

if __name__ == "__main__":
import asyncio
asyncio.run(main())

这时候执行 main.py 就抛出了异常

RuntimeError: Timeout context manager should be used inside a task

打断点查看,确实是在 with timer 抛出的错误,这情况很有可能是没在事件循环内实例化 session。代码少还是很容易看出来,运行事件循环前导入了 http,并且实例化了 session。

所以从根本上上解决问题就是导入http时不要初始化 session,然后代码可以改成这样。

http.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import aiohttp

class Http:
def __init__(self):
self.session = None

async def get_session(self):
if self.session is None:
connector = aiohttp.TCPConnector()
self.session = aiohttp.ClientSession(connector=connector)

return self.session

async def get(self, url):
session = await self.get_session()
async with session.get(url) as r:
return await r.text()

http = Http()

这样就可以就可以放心的在任何地方初始化了。

为什么不能运行前实例化session。

运行事件循环前也可以实例化 session。但是不要使用 asyncio.run 方法,可以自己创建一个loop来运行事件循环。

1
2
3
4
5
6
async def main():
...

if __name__ == "__main__":
loop = async.get_event_loop()
loop.run_until_complete(main())

为什么会这样,我们要进入 asyncio 内部看一下了,CPython 有用 Python 实现的 asyncio 代码,就不用直接看C了。在看过 aiohttp 代码后,aiohttp 初始化 session 时,使用的是 asyncio.get_event_loop() ,asyncio.run() 是自己创建的事件循环。那么我将代码简化后写出来再分析一下。

runners.py
1
2
3
4
5
6
7
8
9
10
11
from events import *
def run()
if events._get_running_loop() is not None:
raise RuntimeError("asyncio.run() cannot be called from a running event loop")

loop = events.new_event_loop()
try:
events.set_event_loop(loop)
return loop.run_until_complete(main)
finally:
...
events.py
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
class _RunningLoop(threading.local):
loop_pid = (None, None)

_event_loop_policy = None
_running_loop = _RunningLoop()

def get_running_loop():
loop = _get_running_loop()
if loop is None:
raise RuntimeError('no running event loop')
return loop

def _get_running_loop():
running_loop, pid = _running_loop.loop_pid
if running_loop is not None and pid == os.getpid():
return running_loop

def _set_running_loop(loop):
_running_loop.loop_pid = (loop, os.getpid())

def _init_event_loop_policy():
global _event_loop_policy
with _lock:
if _event_loop_policy is None: # pragma: no branch
from . import DefaultEventLoopPolicy
_event_loop_policy = DefaultEventLoopPolicy()

def get_event_loop_policy():
if _event_loop_policy is None:
_init_event_loop_policy()
return _event_loop_policy

def get_event_loop():
current_loop = _get_running_loop()
if current_loop is not None:
return current_loop
return get_event_loop_policy().get_event_loop()

aiohttp使用get_event_loop,就是说如果不调用 set_event_loop,当执行 asyncio.run 时,会重新创建一个事件循环,导致事件循环不是同一个,运行事件循环时,aiohttp 里抛出,所以启动事件循环时,也使用 get_event_loop 就能保证最后使用的是同一个事件循环,当然还是不建议这么做,稍微控制不好就会耽误很长的时间找问题所在,最后得不偿失了。

参考

2022年03月26日更新

又一年过去了,我自己的搬瓦工也到期了,一年中维护服务也是比较麻烦的,时不时端口被屏蔽,也有封IP的风险。现在已经换到JustMySocks了,搬瓦工自己的服务,比较稳定,线路也不错,多人开车还是挺划算的,当然用别的机场就要放弃一些安全,建议买$9.88/M的,五条线路无限设备。如果购买可以使用我的推荐链接https://justmysocks.net/members/aff.php?aff=20643


2021年03月12日更新

目前已经换到了搬瓦工,AkkoCloud毕竟是小服务商,虽然说线路不错,但是没事断个网也是很难受的,非重度用户不影响。如果使用搬瓦工也可以使用我的推荐链接https://bandwagonhost.com/aff.php?aff=59518,非常感谢。


之前聊过如何科学上网,如果不了解可以先看一下《如何科学上网》。科学上网要有一个好的服务器,所以我推荐AkkoCloud,这也是我在使用的服务器,如果想购买可以使用我的推荐链接https://www.akkocloud.com/aff.php?aff=256注册购买,非常感谢。

AkkoCloud介绍

这家服务商还比较新,知道的人也比较少,所以不会像搬瓦工那样,便宜的服务器经常处于售空的状态,即便补货你也是抢不到。

线路和带宽

翻墙肯定是要稳定,推荐美国圣何塞CN2 GIA,如果你不知道什么是CN2 GIA可以谷歌搜索一下,CN2线路还是很稳定的,高峰期基本不会丢包。更重要的是,三网优化。

带宽最低是50mbps,双向600G流量,油管2k基本无压力,最大是150mbps,1500G流量。当然越大的价格越贵。

奈非?HBO?

比较不错的是,AkkoCloud基本上是可以解锁流媒体的,可以看Netflix、HBO和Disney+,亲身体验是没问题的,但是官方不保证可以解锁流媒体服务,如果需要换IP,也可以加钱更换。

价格与付款

支持支付宝支付,最便宜的50元一个月。不时会推出一个活动,打个八折,还是非常优惠的。建议买75元一个月的,带宽能达到100mbps。

关于注册

手机号可以不填,邮箱最好填真实的,虽然说不会验证你的邮箱,但是是用来接收invoice的。

MC服务器

AkkoCloud整体偏向于二次元,所以还提供我的世界服务器托管,10块钱一个月,太良心了。

为什么翻墙?

和世界接轨。

后记

最近发现搬瓦工有个补货监控网站,网站链接https://status.bwgyhw.cn/,他们也有TG群的补货通知,感兴趣的加入。如果有买搬瓦工服务器的,可以点击我的邀请链接https://bandwagonhost.com/aff.php?aff=59518,在此谢过了。