不写一行代码,在 Solana 上进行 SPL Token 发币

按照之前的文章,本地安装 Solana CLI 之后,会有一个 spl-token 的命令。
在发币之后需要先了解一下 Solana SPL 相关的概念。

类似于 Ethereum 的 ERC20 程序, Solana 官方提供了一个 SPL Token 程序,专门是用于发币的,对应的 Program ID 是 TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

创建 Mint Account

先创建一个 Mint Account,用于后续的铸币。这里我们为自己的 token 设置精度为6位小数。

1
2
3
4
5
6
7
$ spl-token create-token --decimals=6
Creating token CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

Address: CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk
Decimals: 6

Signature: 3f8q7Dserwzf4NHeCbmgCepGfBYLtuimi8tznTBunbKTwdFqHRMqr6iR1kTc8hfKF5DPtAeveQ4EjBzmQWbXLCjb

创建 Associated Token Account

再创建一个ATA(Associated Token Account),用于接收铸造的币。

1
2
3
4
5
# 这里为刚刚的 Mint Account 创建 ATA
$ spl-token create-account CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk
Creating account EvvQiKLYW67mGSb6oDzGEimJMjGYb1No3ppQWZgB84v

Signature: 3mRP9mMtTZZSFJnWXAvCffHVgQF3jdgecAqnmUTSTVoi51HyfWoe3fvWWeC3v71XML9h3mwjwrnpG8zquXrgy1b2

铸币

往刚刚创建的 ATA 账号发10个币;因为我们的 token 有6位小数,所以实际发的币需要乘以 10*6。

1
2
3
4
5
6
$ spl-token mint CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk 10000000 EvvQiKLYW67mGSb6oDzGEimJMjGYb1No3ppQWZgB84v
Minting 10000000 tokens
Token: CuDVhtzgm9A9Pjdfvv98bph2noKAWf22Z1FBbLK1DnAk
Recipient: EvvQiKLYW67mGSb6oDzGEimJMjGYb1No3ppQWZgB84v

Signature: 87psf5aZak7BCQriScEkioqTW2ozvJn5K6mmjTibstVTWofwiKom3FvWGkkiddJ6oq8KUa2vLkYSKku1r87wdst

至此 Solana 的基本发币操作就完成了。

Solana Rust 开发环境搭建

开发环境安装

rust

1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

我是用的 mise 工具来安装的

1
mise install rust@1.92.0

surfpool

solana-test-validator 的一个替代方案,用于在本地运行开发测试链。
https://github.com/txtx/surfpool

Anchor CLI (可选)

solana 的一个开发框架,也可以直接使用 rust 的 原生 sdk 进行开发。

1
cargo install anchor-cli

Solana CLI

用于构建、部署程序

1
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

然后还需要配置一下环境变量

1
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"

查看当前的账号:

1
solana address

若当前还没有账号,则需要先创建一个:

1
solana-keygen new -o ~/.config/solana/id.json

配置当前使用本地网络:

1
solana config set --url http://localhost:8899

查询当前的配置:

1
solana config get

给自己的账户空投一些 SOL 用于后续的测试:

1
solana airdrop 10

开发流程

创建项目

1
2
3
cargo new --lib <name>
cd <name>
cargo add pinocchio

修改 Cargo.toml,添加内容:

1
2
3
4
5
[package]
edition = "2021" # <-- 这个要改为 2021

[lib]
crate-type = ["cdylib", "lib"]

然后修改 src/lib.rs,编写程序逻辑。

编译

1
cargo build-bpf

编译成功会生成 target/deploy/xxx.so、 target/deploy/xxx.json 私钥两个文件

部署

1
solana program deploy target/deploy/<name>.so

执行成功会输出对应的 Program Id 和 Transaction Id

Arbitrum Stylus 开发环境搭建

开发环境安装

rust

1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

我是用的 mise 工具来安装的

1
mise install rust@1.92.0

Stylus CLI

Arbitrum 的开发框架

1
cargo install cargo-stylus

因为 Arbitrum 的合约程序最终会被编译为 webassembly,所以还需要安装 wasm 相关的编译工具

1
rustup target add wasm32-unknown-unknown

foundry

foundry 是一个 EVM 智能合约开发测试的工具。
下载 foundry 相关的命令行工具: https://github.com/foundry-rs/foundry/releases

solc

下载 solc 的二进制文件: https://github.com/argotorg/solidity/releases

本地开发结点

需要先安装 docker 环境

1
2
3
git clone https://github.com/OffchainLabs/nitro-devnode.git
cd nitro-devnode
./run-dev-node.sh

本地结点的账号、密钥:

1
2
Address: 0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E
Private key: 0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659

如果想要外部也能够连接本地的结点,那么需要修改一个 run-dev-node.sh 这个脚本,在 docker run 这条命令后面再加上 --http.vhosts=* 参数。

开发流程

创建项目

1
cargo stylus new <name>

编译

1
cargo stylus build

编译成功会生成 target/wasm32-unknown-unknown/release/xxx.wasm 文件

部署

验证是否能够部署

1
cargo stylus check -e http://127.0.0.1:8547

仅计算部署所需的 gas 费

1
2
3
4
cargo stylus deploy \
--endpoint=http://127.0.0.1:8547 \
--private-key=<YOUR_PRIVATE_KEY> \
--estimate-gas-only

部署合约

1
2
3
cargo stylus deploy \
--endpoint=http://127.0.0.1:8547 \
--private-key=<YOUR_PRIVATE_KEY>

导出合约 contract ABI,需要先安装 solc

1
cargo stylus export-abi --output=./abi.json --json

合约测试

调用合约程序查询数据:

1
cast call --rpc-url http://127.0.0.1:8547  <合约地址> "合约函数签名" [<函数参数>]

例如,对于一个 ERC20 的程序,查询账户余额:

1
cast call --rpc-url $ARB_RPC_URL $CONTRACT_TOKEN "balanceOf(address)(uint256)" $PUBLIC_KEY

调用合约创建交易:

1
cast send --rpc-url http://127.0.0.1:8547 --private-key <自己的私钥> <合约地址> "合约函数签名" [<函数参数>]

例如,一个 ERC20 的转账操作:

1
cast send --rpc-url $ARB_RPC_URL --private-key $PRIVATE_KEY $CONTRACT_TOKEN "transfer(address, uint256)" $TARGET_ADDRESS $AMOUNT

Bitbucket Runner 配置

以前有介绍过 Github Runner 和 Gitlab Runner 的配置,现在再来介绍一下 Bitbucket Runner 的配置。

参考官方文档: https://support.atlassian.com/bitbucket-cloud/docs/runners/

添加 Runner

首先禁用 Linux 系统的 swap 功能。若不禁用 swap,在 runner 进行 builder 时会使用 swap 内存,可能会出现内存不足的错误。

1
sudo swapoff -sv

编辑 /etc/sysctl.conf 添加 vm.swappiness = 1 再重启系统。

接着在 Bitbucket 的设置界面添加 Runner,然后执行界面给出的命令。
然后在目标机器上执行命令:

1
2
3
4
5
6
7
8
9
docker run-it -d \
--name bitbucket-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-e ACCOUNT_UUID=<accountUuid> \
-e REPOSITORY_UUID=<repositoryUuid> \
-e RUNNER_UUID=<runnerUuid> \
-e OAUTH_CLIENT_ID=<OAuthClientId> \
-e OAUTH_CLIENT_SECRET=<OAuthClientSecret> \
wusuopu/bitbucket-pipelines-runner:1.512

使用 Runner

bitbucket.pipelines.yml 中的相关步骤配置 runs-on,如:

1
2
3
4
5
6
7
pipelines:
default:
- step:
runs-on:
- self.hosted
- linux.shell
script:

因为 runner 是以 linux-shell 的方式运行的,所以会有一些限制: https://support.atlassian.com/bitbucket-cloud/docs/set-up-runners-for-linux-shell/

记录在docker中运行whenever遇到的问题

最近在将一个 rails 的项目移到 docker 方式来部署。这个项目有用到了 whenever 来执行定时任务,这里记录一下在迁移过程中遇到的一些问题。

因为 whenever 是使用系统的 cron 来实现的定时任务,所以直接就在 docker image 内安装一个 cron 即可: apt-get install -y cron
然后尝试执行命令: whenever -i; crontab -l 看看定时任务都有设置正确。最后在容器启动的时候使用命令 service cron start 一并将 cron 也启动。

至此感觉事情很简单,一切都很顺利。然而过了两天客户反馈说是这两天的定时任务都没有执行。
定时任务没执行,那就是 cron 有问题。现在来开始排查问题。

先再安装 syslog: apt-get install -y rsyslog,将 cron 的日志保存下来,方便查找错误。
然后往 crontab 中随便添加一条任务,每分钟执行一次命令: date >> /tmp/date.log

结果1分钟之后 cron 的日志显示有报如下错误:

1
FAILED to open PAM security session (Cannot make/remove an entry for the specified session)

经过搜索知道在 docker 内是没有 session 的,所以 PAM set_loginuid 会失败。需要将 set_loginuid 这行注释掉:

1
sed -i '/^session\s\+required\s\+pam_loginuid.so/c\#session required pam_loginuid.so' /etc/pam.d/cron

重启 cron 之后,date >> /tmp/date.log 定时任务也有正常执行了。

到此感觉问题应该是解决了。然而又过了一天客户还是说定时任务没有执行。
看来还是存在问题的,还得接着排查。

whenever 的任务是定时执行一些 rake 任务,然而执行的结果没有任何的日志。
于是我就新添加了一条 crontab 任务,将 rake 命令的 stdout 和 stderr 重定向到日志文件中。
然后就发现其实是因为在执行 rake 命令时提示 rake 命令不存在,从而导致执行失败的。
看来就因为 PATH 环境变量的问题。

在启动 container 时是有设置了一些环境变量的,然而 cron 这个进程并没有继承这些变量。
所以现在就需要手动为 cron 再配置环境变量。在启动 cron 之前先执行命令:

1
2
3
4
rm /etc/environment
for variable_value in $(cat /proc/1/environ | sed 's/\x00/\n/g'); do
echo $variable_value >> /etc/environment
done

现在再测试一切就正常了。
至此问题终于是解决了,于是就记录一下方便以后查阅。

Gitlab Runner 配置

前一篇文章有介绍了 Github Runner 的配置,现在再来介绍一下 Gitlab Runner 的配置。

关于添加自己的机器,可参考 Gitlab 官方文档: https://docs.gitlab.com/runner/register/index.html

同样的,为了方便,我们使用 docker 来一键部署。首先到 gitlab 的 CI/CD 项目设置页面,然后查看当前项目的 runner 注册 token。
然后执行如下命令进行 runner 注册。

1
2
3
4
5
6
7
8
9
10
11
12
docker run --rm -it -v $PWD/tmp/gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:alpine register
--non-interactive \
--url "https://gitlab.com/" \
--token "PROJECT_REGISTRATION_TOKEN" \
--executor "docker" \
--docker-image alpine:latest \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
--description "docker-runner" \
--tag-list "docker,linux" \
--run-untagged="true" \
--locked="false" \
--access-level="not_protected"

注意这里的 PROJECT_REGISTRATION_TOKEN 需要替换为对应的 token;
--run-untagged 要设为 true,才能运行所有的 job。否则就只会执行打了 tag 的 job 。
如果需要多个项目共享该 runner,那么 --locked 就设为 false;

注册成功之后会生成 ./tmp/gitlab-runner-config/config.toml 文件:

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
concurrent = 1
check_interval = 0

[session_server]
session_timeout = 1800

[[runners]]
name = "runner-docker-01"
url = "https://gitlab.com/"
token = "xxxxxxxxxxxxxxxxxxxx"
executor = "docker"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
[runners.docker]
host = "unix:///var/run/docker.sock"
tls_verify = false
image = "alpine"
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
shm_size = 0

如果更换服务器了,但是还想要继续使用该 runner 的配置,只需要保留该配置文件即可。

以上是 runner 注册成功,接下来就使用该配置文件来启动 runner 服务吧。
同样也是为了安全起见,我们使用 docker-in-docker 的方式来部署。
创建一个 docker-compose.yml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: "2"

services:
dind:
image: docker:19.03-dind
privileged: true
# 使用国内docker镜像源
command: ["--registry-mirror", "https://hub-mirror.c.163.com"]
volumes:
- ./tmp/docker-lib:/var/lib/docker
- ./tmp/docker-run:/var/run

gitlab-runner:
image: gitlab/gitlab-runner:alpine
depends_on:
- dind
volumes:
- ./tmp/docker-run:/var/run
- ./tmp/gitlab-runner-config:/etc/gitlab-runner

然后执行命令 docker-compose up -d 启动服务即可。

Github Runner 配置

在之前的文章《Github Actions使用》有介绍了Github Actions的使用方法。
之前是直接的 Github 官方提供的 runner,来执行。不过官方提供的 runner 多少还是有些限制。
这次我们来尝试将自己的 server 配置成 runner 来执行 actions。

关于添加自己的机器,可参考 Github 官方文档: https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/adding-self-hosted-runners

在 github 中 runner 分为三个级别: repository, organization, enterprise。
其中 repository 级别的 runner 就只能在这个代码仓库下使用;
organization 和 enterprise 级别的 runner 可以在这个 organization 或者 enterprise 下的所有代码仓库中共享。

由于我是免费用户,这里我就以 organization 为例吧。另外为了方便我们就直接通过 docker 来运行。

参考官方文档,首先进入到 organization 的 actions 设置页面,然后选择添加一个 runner。没有 organization 的可以自己创建一个。
接着在自己的 server 上执行命令:

1
2
3
4
5
6
docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
-e GH_REPOSITORY=xxxxxxxxxxxx \
-e GH_RUNNER_TOKEN=xxxxxxxxxxxxx \
-e GH_RUNNER_LABELS=label1,label2 \
wusuopu/github-actions-runner:2.273.6

其中 GH_REPOSITORY 就是设置页面上显示的 url 参数,GH_RUNNER_TOKEN 就是 token 参数。GH_RUNNER_LABELS 则根据自己的需要来为该 runner 设置标签。

至此,自己的 runner 就已经配置好了。不过这里我们是直接在这个 runner 的 container 中操作 server 上的 docker 服务。这样其实是非常危险的,尤其是对于公有仓库来说。

为了安全起见,我们可以再运行一个 Docker-in-Docker 的服务,然后这个就只需要连接到该服务即可。
创建一个 docker-compose.yml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: "2"

services:
dind:
image: docker:19.03-dind
privileged: true
volumes:
- ./tmp/docker-lib:/var/lib/docker
- ./tmp/docker-run:/var/run

github-runner:
image: wusuopu/github-actions-runner:2.273.6
depends_on:
- dind
environment:
- GH_REPOSITORY=https://github.com/xxxx
- GH_RUNNER_TOKEN=xxxxxxxxxxxxx
- GH_RUNNER_LABELS=dind
volumes:
- ./tmp/docker-run:/var/run

然后执行命令 docker-compose up -d 启动服务即可。
如果之后 actions 任务太多处理不过来,可以将 runner 扩容,多增加几个 runner 实例: docker-compose scale github-runner=3

至此,我们自己的 runner 就配置好了。如果想要在 actions 中使用我们自己的 runner,则需要修改之前的配置文件 .github/workflows/main.yml。将 runs-on: ubuntu-latest 改为 runs-on: self-hosted

使用python编写帝国时代2修改器

之前的文章有介绍了《使用python编写游戏修改器》
最近怀旧一下,在玩《帝国时代2》,然后就尝试着再写一个修改器试试。

查找内存地址

首先用 Cheat Engine 来修改资源,以及查找资源的内存地址。注意这里的数据都要使用浮点数类型。
如图找到的食物内存地址是 0F2B93B0
age2_ce01

通过逐步分析得到以下指标的内存地址为:
木材地址 = 食物地址 + 4
石头地址 = 食物地址 + 8
黄金地址 = 食物地址 + 12
黄金地址 = 食物地址 + 12
人口上限地址 = 食物地址 + 16
当前人口地址 = 食物地址 + 44

如果想要实际无限人口,只需要修改 人口上限 和 当前人口 这两个数据即可。
这些地址都找到之后,然后就开始写代码,使用 Python 调用 WriteProcessMemory 来修改游戏的内存。
然而在打完这一局之后开启新的游戏时却发现这些地址全都变了。
每次开启新的游戏时都需要重新查找一遍地址内存,这个就有点麻烦了。

查找游戏基址

通过分析发现这些地址都全是动态地址,每次游戏时都不一样。根据仅记得的一点汇编知识知道,这些动态地址是通过基址加偏移地址得到的。所以现在一劳永逸的方法是找到基址地址。而基址是静态的。然后接下来逐步进行分析。

首先刚刚食物地址是0F2B93B0。游戏程序如果要从这个地址获取食物数据,那么就得在某个地方储存这个地址值。这里我们就先将这个地址称为食物地址指针。接着用 CE 查找一下 0F2B93B0 这个值,然后就找到了食物地址指针地址为 206221B8。如图:
age2_ce02
注意这里要用 16 进制的方式来查找这个地址值。如果用 C 语言代码的描述相当于:

1
2
3
int food_point = 0x206221B8       // 食物地址指针
int food_address = *food_point; // 食物地址指针 指向食物地址 0x0F2B93B0
int food = *food_point; // 从食物地址 0x0F2B93B0 得到食物的值

经过测试发现这个食物地址指针地址也是动态的。没办法了,只能继续往下找。现在来看看都有谁访问了这个地址。如图:
age2_ce03
age2_ce04
这里我们又得到了一个地址: 20622110 。再来看看是哪里记录了这个地址呢。如图:
age2_ce05
这里我们又找到了 007A5FEC 这个地址,运气不错。在 CE 中显示为绿色,表示它是一个静态地址。这表示它就是我们要找的基址了。

总结

现在来总结一下。使用 C 语言代码描述如下:

1
2
3
4
int base_addr = 0x007A5FEC;             // 游戏基址
int food_point = *base_addr + 0xA8; // 基址记录的值0x20622110 + 偏移地址0xA8 = 食物地址指针
int food_address = *food_point; // 得到食物地址
int food = *food_address; // 从食物地址 0x0F2B93B0 得到食物的值

最终修改的结果如图:
age2_ce06

上面修改器的完整源代码,如有需要可通过以下链接获取:
https://github.com/wusuopu/cheat_engine_age2

使用Vagrant和Docker搭建Kubernetes集群

在之前的文章《使用Vagrant在Ubuntu系统上搭建Kubernetes集群》介绍了使用 Vagrant 在 VirtualBox 中安装 Ubuntu 系统搭建 Kubernetes 集群。
因为 Vagrant 是支持 Docker 的,所以这篇文章就来尝试不再使用 VirtualBox 了,而是直接使用 Docker 来搭建 Kubernetes 集群。

安装依赖

需要先安装以下程序:

  • Vagrant
  • Docker
  • Kubectl

运行集群

因为是在本地学习 k8s,因此为了方便我就使用 Rancher 的 k3s 来进行安装。
下载 Vagrant 配置文件: https://github.com/wusuopu/kubernetes-vagrant-alpine

然后执行命令:

1
vagrant up --no-parallel --provision

这里启动 k3s 服务,并将 master node 的 /etc/rancher/k3s/k3s.yaml 文件内容复制到 host 系统上来,这样就可以直接在 host 系统中用 kubectl 来操作集群。

1
2
3
4
5
6
-> % k get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
alpine-02 Ready <none> 59m v1.17.4-k3s1 172.17.0.3 <none> Alpine Linux v3.11 4.9.184-linuxkit docker://19.3.8
alpine-01 Ready master 59m v1.17.4-k3s1 172.17.0.2 <none> Alpine Linux v3.11 4.9.184-linuxkit docker://19.3.8
alpine-03 Ready <none> 58m v1.17.4-k3s1 172.17.0.4 <none> Alpine Linux v3.11 4.9.184-linuxkit docker://19.3.8

这里集群的各个 node 都是一个 docker container。

1
2
3
4
5
-> % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a91d13e2cca0 wusuopu/vagrant:k3s-alpine "dockerd-entrypoint.…" About an hour ago Up 35 minutes 2375-2376/tcp, 6443/tcp, 127.0.0.1:2201->22/tcp kubernetes-vagrant-alpine_alpine-03_1585887389
cce93873b666 wusuopu/vagrant:k3s-alpine "dockerd-entrypoint.…" About an hour ago Up 35 minutes 2375-2376/tcp, 6443/tcp, 127.0.0.1:2200->22/tcp kubernetes-vagrant-alpine_alpine-02_1585887382
d4844843ca3e wusuopu/vagrant:k3s-alpine "dockerd-entrypoint.…" About an hour ago Up 36 minutes 2375-2376/tcp, 0.0.0.0:6443->6443/tcp, 127.0.0.1:2222->22/tcp kubernetes-vagrant-alpine_alpine-01_1585887365

Github Actions使用

最近在看 Github Actions,相对一 Gitlab CI 来说,它的配置就没有那么直观了。

以下通过一个例子来说下 Github Actions 的用法。

在项目根目录下创建文件 .github/workflows/main.yml

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
name: CI

on:
# 仅在有以 'v' 开头的 tag push 时才会触发
push:
tags:
- v*

jobs:
build:
name: build docker image
runs-on: ubuntu-latest

steps:
# 切换到当前的 commit
- uses: actions/checkout@v2
with:
ref: ${{ github.ref }}
- name: build docker image
env:
canister_user: ${{ secrets.canister_user }}
canister_password: ${{ secrets.canister_password }}
docker_image_name: cloud.canister.io:5000/${{ github.repository }}
run: |
echo $GITHUB_WORKSPACE $GITHUB_REF
cd $GITHUB_WORKSPACE
docker build -t $docker_image_name:${GITHUB_REF##*/} .
docker login -u $canister_user -p $canister_password cloud.canister.io:5000
docker push $docker_image_name:${GITHUB_REF##*/}

以上的配置执行的操作是,当有新的 ‘v’ 开头的 tag push 时,则 build docker image 并 push 到 cloud.canister.io 的私有 docker registry 上。

这里 canister 的用户名和密码是通过 secrets 来配置的,在 github 的 repository -> Settings -> Secrets 页面。

参考: https://help.github.com/en/actions