├── .circleci
└── config.yml
├── .github
├── pull_request_template.md
└── workflows
│ ├── build_upload.yml
│ ├── docker_image.yml
│ └── go.yml
├── .gitignore
├── CHANGELOG.md
├── FUNDING.json
├── LICENSE-APACHE
├── LICENSE-MIT
├── Makefile
├── README.md
├── SECURITY.md
├── api
├── api_common.go
├── api_miner.go
├── client
│ ├── client.go
│ ├── gateway_client.go
│ └── submitblock_client.go
├── docgen
│ ├── example.go
│ └── main.go
└── proxy_gen.go
├── build
├── clock.go
├── tools.go
└── version.go
├── cli
├── cmd.go
├── helper.go
├── log.go
└── version.go
├── cmd
├── address.go
├── init.go
├── main.go
├── record.go
├── run.go
├── stop.go
└── winner.go
├── docs
├── en
│ ├── api-v0-methods-miner.md
│ ├── config-desc.md
│ ├── jaeger-tracing.md
│ └── metrics.md
└── zh
│ ├── config-desc.md
│ ├── design-spec.md
│ ├── getting-start.md
│ ├── journal-event.md
│ ├── metrics.md
│ └── mine-record.md
├── f3participant
├── multiparticipation.go
└── participation.go
├── gen
└── api
│ └── proxygen.go
├── go.mod
├── go.sum
├── lib
├── journal
│ ├── alerting
│ │ ├── alerts.go
│ │ └── alerts_test.go
│ ├── env.go
│ ├── fsjournal
│ │ ├── fs.go
│ │ └── fs_test.go
│ ├── mockjournal
│ │ └── journal.go
│ ├── nil.go
│ ├── registry.go
│ ├── registry_test.go
│ └── types.go
├── logger
│ └── levels.go
├── metrics
│ ├── expoter.go
│ └── metrics.go
└── tracing
│ └── setup.go
├── miner
├── api.go
├── miner_test.go
├── minerwpp.go
├── miningmgr.go
├── mock
│ └── mock_post_provider.go
├── multiminer.go
├── util.go
└── warmup.go
├── node
├── builder.go
├── config
│ ├── apiinfo.go
│ ├── apiinfo_test.go
│ ├── def.go
│ ├── def_test.go
│ ├── gatewaydef.go
│ ├── load.go
│ ├── load_test.go
│ └── migrate
│ │ └── def_nv170.go
├── impl
│ ├── common
│ │ └── common.go
│ └── miner.go
├── modules
│ ├── helpers
│ │ └── helpers.go
│ ├── mine-recorder
│ │ ├── inner_recorder.go
│ │ ├── mod.go
│ │ └── record_test.go
│ ├── miner-manager
│ │ ├── api.go
│ │ ├── auth_manager.go
│ │ └── mock
│ │ │ └── miner_manager.go
│ ├── miner.go
│ ├── services.go
│ ├── slashfilter
│ │ ├── api.go
│ │ ├── local.go
│ │ ├── mock.go
│ │ ├── slashfilter.go
│ │ └── slashfilter_test.go
│ └── storage.go
├── options.go
└── repo
│ ├── fsrepo.go
│ ├── fsrepo_ds.go
│ ├── fsrepo_test.go
│ ├── interface.go
│ ├── migrate.go
│ └── repo_test.go
├── scripts
├── build-bundle.sh
└── publish-release.sh
└── types
├── api.go
├── logs.go
├── miner.go
├── shutdown.go
└── storage.go
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## 关联的Issues (Related Issues)
2 |
3 |
4 |
5 | close
6 |
7 | ## 改动 (Proposed Changes)
8 |
9 |
10 |
11 |
12 | ## 附注 (Additional Info)
13 |
14 |
15 |
16 | ## 自查清单 (Checklist)
17 |
18 | 在你认为本 PR 满足被审阅的标准之前,需要确保 / Before you mark the PR ready for review, please make sure that:
19 | - [ ] 符合Venus项目管理规范中关于PR的[相关标准](https://github.com/ipfs-force-community/dev-guidances/blob/master/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86/Venus/PR%E5%91%BD%E5%90%8D%E8%A7%84%E8%8C%83.md) / The PR follows the PR standards set out in the Venus project management guidelines
20 | - [ ] 具有清晰明确的[commit message](https://github.com/ipfs-force-community/dev-guidances/blob/master/%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86/%E4%BB%A3%E7%A0%81/git%E4%BD%BF%E7%94%A8/commit-message%E9%A3%8E%E6%A0%BC%E8%A7%84%E8%8C%83.md) / All commits have a clear commit message.
21 | - [ ] 包含相关的的[测试用例](https://github.com/ipfs-force-community/dev-guidances/blob/master/%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86/%E4%BB%A3%E7%A0%81/%E4%BB%A3%E7%A0%81%E5%BA%93/%E6%A3%80%E6%9F%A5%E9%A1%B9/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95.md)或者不需要新增测试用例 / This PR has tests for new functionality or change in behaviour or not need to add new tests.
22 | - [ ] 包含相关的的指南以及[文档](https://github.com/ipfs-force-community/dev-guidances/tree/master/%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86/%E6%96%87%E6%A1%A3)或者不需要新增文档 / This PR has updated usage guidelines and documentation or not need
23 | - [ ] 通过必要的检查项 / All checks are green
24 |
--------------------------------------------------------------------------------
/.github/workflows/build_upload.yml:
--------------------------------------------------------------------------------
1 | name: build and upload
2 |
3 | on:
4 | push:
5 | branches: ['prep/**', 'release/**', 'test/**', master]
6 | tags: ['**']
7 |
8 | jobs:
9 | build_upload:
10 | uses: filecoin-project/venus/.github/workflows/common_build_upload.yml@master
11 | with:
12 | bin_name: 'sophon-miner'
13 | has_ffi: false
14 | secrets:
15 | OSS_KEY_ID: ${{secrets.OSS_KEY_ID}}
16 | OSS_KEY_SECRET: ${{secrets.OSS_KEY_SECRET}}
17 | OSS_ENDPOINT: ${{secrets.OSS_ENDPOINT}}
18 | OSS_BUCKET: ${{secrets.OSS_BUCKET}}
19 | FTP_HOST: ${{secrets.FTP_HOST}}
20 | FTP_USER: ${{secrets.FTP_USER}}
21 | FTP_PWD: ${{secrets.FTP_PWD}}
22 | GODEYE_URL: ${{secrets.GODEYE_URL}}
23 | token: ${{github.token}}
24 |
--------------------------------------------------------------------------------
/.github/workflows/docker_image.yml:
--------------------------------------------------------------------------------
1 | name: Docker Image CI
2 |
3 | on:
4 | push:
5 | branches: ['prep/**', 'release/**', 'test/**', master]
6 | tags: ['**']
7 |
8 | jobs:
9 |
10 | build_docker_image:
11 | uses: filecoin-project/venus/.github/workflows/common_docker_image.yml@master
12 | secrets:
13 | DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
14 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches:
8 | - '**'
9 |
10 | jobs:
11 |
12 | check:
13 | uses: filecoin-project/venus/.github/workflows/common_go.yml@master
14 | with:
15 | has_ffi: false
16 | test_timeout: 20
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /sophon-miner
2 | /bench.json
3 | /lotuspond/front/node_modules
4 | /lotuspond/front/build
5 | extern/filecoin-ffi/rust/target
6 | **/*.a
7 | **/*.pc
8 | /**/*/.DS_STORE
9 | .DS_STORE
10 | build/.*
11 | build/paramfetch.sh
12 | /vendor
13 | /blocks.dot
14 | /blocks.svg
15 | /bundle
16 | /darwin
17 | /linux
18 |
19 | *-fuzz.zip
20 | bin/ipget
21 | bin/tmp/*
22 | .idea
23 | .vscode
24 | scratchpad
25 | .vscode
26 | *.out
27 | dockerfile
28 | sophon-miner.log
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # sophon-miner changelog
2 |
3 | ## v1.15.0
4 |
5 | * Feat/add more metrics [[#238](https://github.com/ipfs-force-community/sophon-miner/pull/238)]
6 |
7 | ## 1.14.0
8 |
9 | * refactor: Only pass the information needed to computeTicket [[#222](https://github.com/ipfs-force-community/sophon-miner/pull/222)]
10 | * add some explanation of parameters / 添加参数注释 [[#221](https://github.com/ipfs-force-community/sophon-miner/pull/221)]
11 | * doc: enable record [[#225](https://github.com/ipfs-force-community/sophon-miner/pull/225)]
12 | * fix: get beacon earlier [[#227](https://github.com/ipfs-force-community/sophon-miner/pull/227)]
13 | * Merge pull request #227 from ipfs-force-community/fix/get-beacon-earlier [[#228](https://github.com/ipfs-force-community/sophon-miner/pull/228)]
14 | * opt: add DialMinerRPC [[#230](https://github.com/ipfs-force-community/sophon-miner/pull/230)]
15 | * chore: bump version to v1.14.0-rc1 [[#232](https://github.com/ipfs-force-community/sophon-miner/pull/232)]
16 | * chore: update venus [[#233](https://github.com/ipfs-force-community/sophon-miner/pull/233)]
17 | * feat: add warning for PropagationDelaySecs [[#234](https://github.com/ipfs-force-community/sophon-miner/pull/234)]
18 | * fix: return error message [[#235](https://github.com/ipfs-force-community/sophon-miner/pull/235)]
19 |
20 | ## 1.13.0 / 2023-08-31
21 |
22 | * Fix/query record incomplete in [[#220](https://github.com/ipfs-force-community/sophon-miner/pull/220)]
23 | * feat: control concurrency when call CountWinner in [[#223](https://github.com/ipfs-force-community/sophon-miner/pull/223)]
24 |
25 | ## 1.13.0-rc2 / 2023-08-18
26 |
27 | ### New Feature
28 | * feat: disable mine record by default [[#218](https://github.com/ipfs-force-community/sophon-miner/pull/218)]
29 |
30 | ## 1.13.0-rc1 / 2023-08-18
31 |
32 | ### New Feature
33 | * feat: add api to list blocks in [[#211](https://github.com/ipfs-force-community/sophon-miner/pull/211)]
34 | * feat: add venus name space header in [[#213](https://github.com/ipfs-force-community/sophon-miner/pull/213)]
35 | * feat: add interface to query record of mining in [[#215](https://github.com/ipfs-force-community/sophon-miner/pull/215)]
36 |
37 | ### Bug Fix
38 | * fix: add null round count in [[#216](https://github.com/ipfs-force-community/sophon-miner/pull/216)]
39 |
40 | ### Documentation And Chores
41 | * doc: update config description / 更新config的解释 in [[#209](https://github.com/ipfs-force-community/sophon-miner/pull/209)]
42 | * chore: remove useless config code / 删除没用的配置代码 in [[#210](https://github.com/ipfs-force-community/sophon-miner/pull/210)]
43 | * chore: merge release v1.12 in [[#208](https://github.com/ipfs-force-community/sophon-miner/pull/208)]
44 |
45 | ## 1.12.0
46 |
47 | ## 1.12.0-rc2 / 2023-06-20
48 |
49 | ### Bug Fixes
50 | * fix: cmds compatible old repo [[#203](https://github.com/ipfs-force-community/sophon-miner/pull/203)]
51 |
52 |
53 | ## 1.12.0-rc1 / 2023-06-14
54 |
55 | ### New Features
56 | * feat: repo migrate [[#197](https://github.com/ipfs-force-community/sophon-miner/pull/197)]
57 |
58 | ### Bug Fixes
59 | * fix: Correctly handle null round [[#198](https://github.com/ipfs-force-community/sophon-miner/pull/198)]
60 | * fix: data race of mockChain [[#199](https://github.com/ipfs-force-community/sophon-miner/pull/199)]
61 |
62 |
63 | ## 1.11.0 / 2023-04-23
64 |
65 | * bump up version to v1.11.0
66 | * update sophon-auth to v1.11.0
67 | * update venus to v1.11.0
68 |
69 | ## 1.11.0-rc1 / 2023-04-18
70 |
71 | ### New Features
72 | * feat: mysql table migrate for miner_blocks / 更新数据库字段为可空或者添加默认值[[#168](https://github.com/ipfs-force-community/sophon-miner/pull/168)]
73 | * feat: update AuthClient which with token /客户端token验证 [[#169](https://github.com/ipfs-force-community/sophon-miner/pull/169)]
74 | * feat: add status api / 添加状态检测接口 [[#172](https://github.com/ipfs-force-community/sophon-miner/pull/172)]
75 | * feat: add docker push /增加推送到镜像仓库的功能 [[#183](https://github.com/ipfs-force-community/sophon-miner/pull/183)]
76 |
77 | ### Improvements
78 | * opt: chain-forked check /链分叉判断时只对同周期内判断 [[#181](https://github.com/ipfs-force-community/sophon-miner/pull/181)]
79 | * opt: set the select messages timeout / 选择消息设置5秒超时 [[#184](https://github.com/ipfs-force-community/sophon-miner/pull/184)]
80 |
81 | ### Bug Fixes
82 | * fix:check gateway fail /修复gateway配置检查失败的问题 [[#177](https://github.com/ipfs-force-community/sophon-miner/pull/177)]
83 | * fix: config check / 修复配置检测失败的问题 [[#178]( https://github.com/ipfs-force-community/sophon-miner/pull/178)]
84 |
85 | ## 1.10.0 / 2023-03-02
86 |
87 | ### Improvements
88 |
89 | - 数据表 `miner_blocks` 字段设置默认值,`winning_at` 允许为 `null`
90 |
91 | ## 1.10.0-rc1 / 2023-02-17
92 |
93 | ### New features
94 | - feat: user data isolation / 增加用户数据隔离 (#163) ([ipfs-force-community/sophon-miner#163](https://github.com/ipfs-force-community/sophon-miner/pull/163))
95 |
96 |
97 | ## 1.9.0 / 2022-12-30
98 |
99 | ### Dependency Updates
100 |
101 | - 升级venus组件的版本
102 |
103 |
104 | ## 1.8.0 / 2022-11-16
105 |
106 | ### Dependency Updates
107 |
108 | - github.com/filecoin-project/venus (-> v1.8.0)
109 | - github.com/ipfs-force-community/sophon-auth (-> v1.8.0)
110 |
111 |
112 | ## 1.8.0-rc5 / 2022-11-03
113 |
114 | ### Improvements
115 |
116 | - 增加 `miners` 是否出块的控制开关,需要 `sophon-auth` 版本 >= v1.8.0-rc4.
117 |
118 |
119 | ## 1.8.0-rc4 / 2022-10-26
120 |
121 | ### Fixes
122 |
123 | - 修复计算历史出块权时panic
124 |
125 | ## 1.8.0-rc3 / 2022-10-20
126 |
127 | ### Improvements
128 |
129 | - 不记录没有获胜的出块Timeout
130 |
131 | ### 注意事项
132 |
133 | 从 `1.7.*` 升级会自动迁移配置文件,从 `1.6.*` 升级需重新初始化`Repo`(init)
134 |
135 |
136 | ## 1.8.0-rc2 / 2022-10-19
137 |
138 | ### Improvements
139 |
140 | - 配置项 `MySQL.ConnMaxLifeTime` 改为字符窜格式: `60 -> "1m0s"`;
141 | - `PropagationDelaySecs` 和 `MinerOnceTimeout` 由配置文件设置;
142 | - Repo目录增加 `version`用于自动升级配置文件等。
143 |
144 | ### 注意事项
145 |
146 | 从 `1.7.*` 升级会自动迁移配置文件,从 `1.6.*` 升级需重新初始化`Repo`(init)
147 |
148 |
149 |
150 | ## 1.6.1 / 2022-07-22
151 |
152 | ### Improvements
153 |
154 | - 网络参数从同步节点请求,移除本地配置;
155 | - 简化配置文件,参考 配置文件解析;
156 | - 移除 `venus-shared` 中已有的数据类型;
157 | - 移除没有实际作用的代码;
158 | - 矿工由 `sophon-auth` 管理,移除本地的矿工管理模块;
159 | - 移除对 `filecoin-ffi` 的依赖;
160 | - 新增配置项说明文档;
161 | - 新增快速启动文档;
162 | - 优化出块,在创建区块前再次尝试获取上一轮`base`,做如下处理:
163 | - 获得较优 `base`(满足获胜条件并有更多的区块),则选择后者进行出块,避免获取 `base` 不足引起的孤块;
164 | - `base` 不匹配(发生了链分叉,之前获得的 `base` 计算的出块权是无效的),不进行无意义的区块创建。
165 | - 修复采用 `mysql` 数据库时 `parenet_key` 字段长度不足问题。
166 |
167 | ### 注意事项
168 |
169 | 升级到此版本涉及配置文件的改动,有两个选择:
170 | - 重新初始化 `repo`;
171 | - 手动修改配置文件(参考config-desc.md)
172 |
--------------------------------------------------------------------------------
/FUNDING.json:
--------------------------------------------------------------------------------
1 | {
2 | "drips": {
3 | "filecoin": {
4 | "ownedBy": "0xD4a3361da05DD7eD15DF0dDE1361b393D48f55F9"
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
2 |
3 | http://www.apache.org/licenses/LICENSE-2.0
4 |
5 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
6 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL=/usr/bin/env bash
2 |
3 | all: build
4 | .PHONY: all docker
5 |
6 | GOVERSION:=$(shell go version | cut -d' ' -f 3 | cut -d. -f 2)
7 | ifeq ($(shell expr $(GOVERSION) \< 17), 1)
8 | $(warning Your Golang version is go 1.$(GOVERSION))
9 | $(error Update Golang to version to at least 1.18.1)
10 | endif
11 |
12 | CLEAN:=
13 | BINS:=
14 |
15 | BUILD_TARGET=sophon-miner
16 |
17 | ldflags=-X=github.com/ipfs-force-community/sophon-miner/build.CurrentCommit='+git$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null))'
18 | ifneq ($(strip $(LDFLAGS)),)
19 | ldflags+=-extldflags=$(LDFLAGS)
20 | endif
21 |
22 | GOFLAGS+=-ldflags="$(ldflags)"
23 |
24 | build: miner
25 | .PHONY: build
26 |
27 | miner:
28 | rm -f $(BUILD_TARGET)
29 | go build $(GOFLAGS) -o $(BUILD_TARGET) ./cmd/
30 |
31 | debug:
32 | rm -f $(BUILD_TARGET)
33 | go build $(GOFLAGS) -gcflags=all="-N -l" -o $(BUILD_TARGET) ./cmd/
34 |
35 | .PHONY: miner
36 | BINS+=sophon-miner
37 |
38 | docsgen:
39 | go build $(GOFLAGS) -o docgen-md ./api/docgen
40 | ./docgen-md > ./docs/en/api-v0-methods-miner.md
41 | rm docgen-md
42 |
43 | # MISC
44 |
45 | buildall: $(BINS)
46 |
47 | clean:
48 | rm -rf $(CLEAN) $(BINS)
49 | .PHONY: clean
50 |
51 | dist-clean:
52 | git clean -xdff
53 | git submodule deinit --all -f
54 | .PHONY: dist-clean
55 |
56 | gen:
57 | go run ./gen/api
58 | goimports -w api
59 | .PHONY: gen
60 |
61 | print-%:
62 | @echo $*=$($*)
63 |
64 |
65 | # docker
66 | .PHONY: docker
67 |
68 | mock:
69 | go run github.com/golang/mock/mockgen -destination=./miner/mock/mock_post_provider.go -source=./miner/util.go -package mock
70 |
71 | TAG:=test
72 | docker:
73 | ifdef DOCKERFILE
74 | cp $(DOCKERFILE) ./dockerfile
75 | else
76 | curl -o dockerfile https://raw.githubusercontent.com/filecoin-project/venus-docs/master/script/docker/dockerfile
77 | endif
78 | docker build --build-arg https_proxy=$(BUILD_DOCKER_PROXY) --build-arg BUILD_TARGET=$(BUILD_TARGET) -t sophon-miner .
79 | docker tag sophon-miner filvenus/sophon-miner:$(TAG)
80 | ifdef PRIVATE_REGISTRY
81 | docker tag sophon-miner $(PRIVATE_REGISTRY)/filvenus/sophon-miner:$(TAG)
82 | endif
83 |
84 | docker-push: docker
85 | docker push $(PRIVATE_REGISTRY)/filvenus/sophon-miner:$(TAG)
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | sophon-miner
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | The sophon-miner is used to mine for multiple miners, the advantage is that:
16 | 1. The miners in the mining pool do not need to deploy their own mining programs.
17 | 2. It is possible to increase the tps and miner fees for packaging messages.
18 | 3. Miners can package messages for each other to avoid the problem of selfish mining.
19 |
20 | Use [Venus Issues](https://github.com/filecoin-project/venus/issues) for reporting issues about this repository.
21 |
22 | ## Building & Documentation
23 |
24 | For instructions on how to build, install and setup venus, please visit [https://venus.filecoin.io/](https://venus.filecoin.io/).
25 |
26 | ## Reporting a Vulnerability
27 |
28 | Please send an email to security@filecoin.org. See our [security policy](SECURITY.md) for more details.
29 |
30 | ## Related packages
31 |
32 | These repos are independent and reusable modules, but are tightly integrated into Venus to make up a fully featured Filecoin implementation:
33 | - [specs-actors](https://github.com/filecoin-project/specs-actors) which has its own [kanban work tracker available here](https://app.zenhub.com/workspaces/actors-5ee6f3aa87591f0016c05685/board)
34 |
35 | ## Contribute
36 |
37 | Venus is a universally open project and welcomes contributions of all kinds: code, docs, and more. However, before making a contribution, we ask you to heed these recommendations:
38 |
39 | 1. If the proposal entails a protocol change, please first submit a [Filecoin Improvement Proposal](https://github.com/filecoin-project/FIPs).
40 | 2. If the change is complex and requires prior discussion, [open an issue](https://github.com/ipfs-force-community/sophon-miner/issues) to request feedback before you start working on a pull request. This is to avoid disappointment and sunk costs, in case the change is not actually needed or accepted.
41 | 3. Please refrain from submitting PRs to adapt existing code to subjective preferences. The changeset should contain functional or technical improvements/enhancements, bug fixes, new features, or some other clear material contribution. Simple stylistic changes are likely to be rejected in order to reduce code churn.
42 |
43 | When implementing a change:
44 |
45 | 1. Adhere to the standard Go formatting guidelines, e.g. [Effective Go](https://golang.org/doc/effective_go.html). Run `go fmt`.
46 | 2. Stick to the idioms and patterns used in the codebase. Familiar-looking code has a higher chance of being accepted than eerie code. Pay attention to commonly used variable and parameter names, avoidance of naked returns, error handling patterns, etc.
47 | 3. Comments: follow the advice on the [Commentary](https://golang.org/doc/effective_go.html#commentary) section of Effective Go.
48 | 4. Minimize code churn. Modify only what is strictly necessary. Well-encapsulated changesets will get a quicker response from maintainers.
49 | 5. Lint your code with [`golangci-lint`](https://golangci-lint.run) (CI will reject your PR if unlinted).
50 | 6. Add tests.
51 | 7. Title the PR in a meaningful way and describe the rationale and the thought process in the PR description.
52 | 8. Write clean, thoughtful, and detailed [commit messages](https://chris.beams.io/posts/git-commit/). This is even more important than the PR description, because commit messages are stored _inside_ the Git history. One good rule is: if you are happy posting the commit message as the PR description, then it's a good commit message.
53 |
54 | ## License
55 |
56 | Dual-licensed under [MIT](https://github.com/ipfs-force-community/sophon-miner/blob/master/LICENSE-MIT) + [Apache 2.0](https://github.com/ipfs-force-community/sophon-miner/blob/master/LICENSE-APACHE)
57 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | For reporting security vulnerabilities/bugs, please consult our Security Policy and Responsible Disclosure Program information at https://github.com/filecoin-project/community/blob/master/SECURITY.md. Security vulnerabilities should be reported via our [Vulnerability Reporting channels](https://github.com/filecoin-project/community/blob/master/SECURITY.md#vulnerability-reporting) and will be eligible for a [Bug Bounty](https://security.filecoin.io/bug-bounty/).
6 |
7 | Please try to provide a clear description of any bugs reported, along with how to reproduce the bug if possible. More detailed bug reports (especially those with a PoC included) will help us move forward much faster. Additionally, please avoid reporting bugs that already have open issues. Take a moment to search the issue list of the related GitHub repositories before writing up a new report.
8 |
9 | Here are some examples of bugs we would consider to be security vulnerabilities:
10 |
11 | * If you can spend from a `multisig` wallet you do not control the keys for.
12 | * If you can cause a miner to be slashed without them actually misbehaving.
13 | * If you can maintain power without submitting windowed posts regularly.
14 | * If you can craft a message that causes venus nodes to panic.
15 | * If you can cause your miner to win significantly more blocks than it should.
16 | * If you can craft a message that causes a persistent fork in the network.
17 | * If you can cause the total amount of Filecoin in the network to no longer be 2 billion.
18 |
19 | This is not an exhaustive list, but should provide some idea of what we consider as a security vulnerability, .
20 |
21 | ## Reporting a non security bug
22 |
23 | For non-security bugs, please simply file a GitHub [issue](https://github.com/filecoin-project/venus/issues/new/choose).
24 |
--------------------------------------------------------------------------------
/api/api_common.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/google/uuid"
7 |
8 | sharedTypes "github.com/filecoin-project/venus/venus-shared/types"
9 | )
10 |
11 | type Common interface {
12 |
13 | // Version provides information about API provider
14 | Version(context.Context) (sharedTypes.Version, error) //perm:read
15 |
16 | LogList(context.Context) ([]string, error) //perm:write
17 | LogSetLevel(context.Context, string, string) error //perm:write
18 |
19 | // trigger graceful shutdown
20 | Shutdown(context.Context) error //perm:admin
21 |
22 | // Session returns a random UUID of api provider session
23 | Session(context.Context) (uuid.UUID, error) //perm:read
24 |
25 | Closing(context.Context) (<-chan struct{}, error) //perm:read
26 | }
27 |
--------------------------------------------------------------------------------
/api/api_miner.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/ipfs-force-community/sophon-miner/types"
7 |
8 | "github.com/filecoin-project/go-address"
9 | "github.com/filecoin-project/go-state-types/abi"
10 | )
11 |
12 | type MinerAPI interface {
13 | Common
14 |
15 | UpdateAddress(context.Context, int64, int64) ([]types.MinerInfo, error) //perm:admin
16 | ListAddress(context.Context) ([]types.MinerInfo, error) //perm:read
17 | StatesForMining(context.Context, []address.Address) ([]types.MinerState, error) //perm:read
18 | CountWinners(context.Context, []address.Address, abi.ChainEpoch, abi.ChainEpoch) ([]types.CountWinners, error) //perm:read
19 | ListBlocks(ctx context.Context, params *types.BlocksQueryParams) ([]types.MinedBlock, error) //perm:read
20 | WarmupForMiner(context.Context, address.Address) error //perm:write
21 | Start(context.Context, []address.Address) error //perm:admin
22 | Stop(context.Context, []address.Address) error //perm:admin
23 |
24 | QueryRecord(ctx context.Context, params *types.QueryRecordParams) ([]map[string]string, error) //perm:read
25 | }
26 |
--------------------------------------------------------------------------------
/api/client/client.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/filecoin-project/go-jsonrpc"
9 |
10 | venus_api "github.com/filecoin-project/venus/venus-shared/api"
11 | "github.com/ipfs-force-community/sophon-miner/api"
12 | )
13 |
14 | const MajorVersion = 0
15 | const APINamespace = "miner.MinerAPI"
16 | const MethodNamespace = "Filecoin"
17 |
18 | // NewCommonRPC creates a new http jsonrpc client.
19 | func NewCommonRPC(ctx context.Context, addr string, requestHeader http.Header) (api.Common, jsonrpc.ClientCloser, error) {
20 | var res api.CommonStruct
21 | closer, err := jsonrpc.NewMergeClient(ctx, addr, MethodNamespace,
22 | []interface{}{
23 | &res.Internal,
24 | },
25 | requestHeader,
26 | )
27 |
28 | return &res, closer, err
29 | }
30 |
31 | // NewMinerRPC creates a new http jsonrpc client for miner
32 | func NewMinerRPC(ctx context.Context, addr string, requestHeader http.Header, opts ...jsonrpc.Option) (api.MinerAPI, jsonrpc.ClientCloser, error) {
33 | requestHeader.Set(venus_api.VenusAPINamespaceHeader, APINamespace)
34 | var res api.MinerAPIStruct
35 | closer, err := jsonrpc.NewMergeClient(ctx, addr, MethodNamespace,
36 | []interface{}{
37 | &res.CommonStruct.Internal,
38 | &res.Internal,
39 | },
40 | requestHeader,
41 | opts...,
42 | )
43 |
44 | return &res, closer, err
45 | }
46 |
47 | // DialMinerRPC is a more convinient way of building client, as it resolves any format (url, multiaddr) of addr string.
48 | func DialMinerRPC(ctx context.Context, addr string, token string, requestHeader http.Header, opts ...jsonrpc.Option) (api.MinerAPI, jsonrpc.ClientCloser, error) {
49 | ainfo := venus_api.NewAPIInfo(addr, token)
50 | endpoint, err := ainfo.DialArgs(venus_api.VerString(MajorVersion))
51 | if err != nil {
52 | return nil, nil, fmt.Errorf("get dial args: %w", err)
53 | }
54 |
55 | if requestHeader == nil {
56 | requestHeader = http.Header{}
57 | }
58 | requestHeader.Set(venus_api.VenusAPINamespaceHeader, APINamespace)
59 | ainfo.SetAuthHeader(requestHeader)
60 |
61 | var res api.MinerAPIStruct
62 | closer, err := jsonrpc.NewMergeClient(ctx, endpoint, MethodNamespace, venus_api.GetInternalStructs(&res), requestHeader, opts...)
63 |
64 | return &res, closer, err
65 | }
66 |
--------------------------------------------------------------------------------
/api/client/gateway_client.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/filecoin-project/go-jsonrpc"
8 |
9 | "github.com/ipfs-force-community/sophon-miner/node/config"
10 |
11 | gatewayAPIV2 "github.com/filecoin-project/venus/venus-shared/api/gateway/v2"
12 | )
13 |
14 | func NewGatewayRPC(ctx context.Context, cfg *config.GatewayNode) (gatewayAPIV2.IGateway, jsonrpc.ClientCloser, error) {
15 | var err error
16 | addrs, err := cfg.DialArgs()
17 | if err != nil {
18 | return nil, nil, fmt.Errorf("could not get DialArgs: %w", err)
19 | }
20 |
21 | var gatewayAPI gatewayAPIV2.IGateway = nil
22 | var closer jsonrpc.ClientCloser
23 | for _, addr := range addrs {
24 | gatewayAPI, closer, err = gatewayAPIV2.NewIGatewayRPC(ctx, addr, cfg.AuthHeader())
25 | if err == nil {
26 | return gatewayAPI, closer, err
27 | }
28 | }
29 |
30 | return gatewayAPI, closer, err
31 | }
32 |
--------------------------------------------------------------------------------
/api/client/submitblock_client.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/filecoin-project/go-jsonrpc"
8 |
9 | "github.com/ipfs-force-community/sophon-miner/node/config"
10 |
11 | v1 "github.com/filecoin-project/venus/venus-shared/api/chain/v1"
12 | )
13 |
14 | func NewFullNodeRPC(ctx context.Context, apiInfo *config.APIInfo) (v1.FullNode, jsonrpc.ClientCloser, error) {
15 | addr, err := apiInfo.DialArgs("v1")
16 | if err != nil {
17 | return nil, nil, fmt.Errorf("could not get DialArgs: %w", err)
18 | }
19 |
20 | return v1.NewFullNodeRPC(ctx, addr, apiInfo.AuthHeader())
21 | }
22 |
--------------------------------------------------------------------------------
/api/docgen/example.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "go/ast"
6 | "go/parser"
7 | "go/token"
8 | "path/filepath"
9 | "reflect"
10 | "strings"
11 | "unicode"
12 |
13 | "golang.org/x/text/cases"
14 | "golang.org/x/text/language"
15 |
16 | "github.com/filecoin-project/go-address"
17 | "github.com/filecoin-project/go-jsonrpc/auth"
18 | "github.com/filecoin-project/go-state-types/abi"
19 |
20 | "github.com/ipfs-force-community/sophon-miner/api"
21 |
22 | sharedTypes "github.com/filecoin-project/venus/venus-shared/types"
23 | )
24 |
25 | var ExampleValues = map[reflect.Type]interface{}{
26 | reflect.TypeOf(auth.Permission("")): auth.Permission("write"),
27 | reflect.TypeOf(""): "string value",
28 | reflect.TypeOf(uint64(42)): uint64(42),
29 | reflect.TypeOf(int(42)): int(42),
30 | reflect.TypeOf(uint(42)): uint(42),
31 | reflect.TypeOf(byte(7)): byte(7),
32 | reflect.TypeOf([]byte{}): []byte("byte array"),
33 | reflect.TypeOf(map[string]string{}): make(map[string]string),
34 | }
35 |
36 | func addExample(v interface{}) {
37 | ExampleValues[reflect.TypeOf(v)] = v
38 | }
39 |
40 | func init() {
41 | addr, err := address.NewIDAddress(1234)
42 | if err != nil {
43 | panic(err)
44 | }
45 |
46 | ExampleValues[reflect.TypeOf(addr)] = addr
47 |
48 | addExample(sharedTypes.APIVersion(1))
49 | addExample(abi.ChainEpoch(10101))
50 | addExample(int64(9))
51 | addExample(true)
52 |
53 | // miner specific
54 | //si := uint64(12)
55 | //addExample(&si)
56 | //addExample(abi.ActorID(1000))
57 | }
58 |
59 | func GetAPIType() (i interface{}, t reflect.Type, permStruct []reflect.Type) {
60 | i = &api.MinerAPIStruct{}
61 | t = reflect.TypeOf(new(struct{ api.MinerAPI })).Elem()
62 | permStruct = append(permStruct, reflect.TypeOf(api.MinerAPIStruct{}.Internal))
63 | permStruct = append(permStruct, reflect.TypeOf(api.CommonStruct{}.Internal))
64 |
65 | return
66 | }
67 |
68 | func ExampleValue(method string, t, parent reflect.Type) interface{} {
69 | v, ok := ExampleValues[t]
70 | if ok {
71 | return v
72 | }
73 |
74 | switch t.Kind() {
75 | case reflect.Slice:
76 | out := reflect.New(t).Elem()
77 | out = reflect.Append(out, reflect.ValueOf(ExampleValue(method, t.Elem(), t)))
78 | return out.Interface()
79 | case reflect.Chan:
80 | return ExampleValue(method, t.Elem(), nil)
81 | case reflect.Struct:
82 | es := exampleStruct(method, t, parent)
83 | v := reflect.ValueOf(es).Elem().Interface()
84 | ExampleValues[t] = v
85 | return v
86 | case reflect.Array:
87 | out := reflect.New(t).Elem()
88 | for i := 0; i < t.Len(); i++ {
89 | out.Index(i).Set(reflect.ValueOf(ExampleValue(method, t.Elem(), t)))
90 | }
91 | return out.Interface()
92 |
93 | case reflect.Ptr:
94 | if t.Elem().Kind() == reflect.Struct {
95 | es := exampleStruct(method, t.Elem(), t)
96 | //ExampleValues[t] = es
97 | return es
98 | }
99 | case reflect.Interface:
100 | return struct{}{}
101 | }
102 |
103 | panic(fmt.Sprintf("No example value for type: %s (method '%s')", t, method))
104 | }
105 |
106 | func exampleStruct(method string, t, parent reflect.Type) interface{} {
107 | ns := reflect.New(t)
108 | for i := 0; i < t.NumField(); i++ {
109 | f := t.Field(i)
110 | if shouldIgnoreField(f, parent) {
111 | continue
112 | }
113 |
114 | caser := cases.Title(language.English)
115 | if caser.String(f.Name) == f.Name {
116 | ns.Elem().Field(i).Set(reflect.ValueOf(ExampleValue(method, f.Type, t)))
117 | }
118 | }
119 |
120 | return ns.Interface()
121 | }
122 |
123 | func shouldIgnoreField(f reflect.StructField, parentType reflect.Type) bool {
124 | if f.Type == parentType {
125 | return true
126 | }
127 |
128 | if len(f.Name) == 0 {
129 | return true
130 | }
131 |
132 | if !token.IsExported(f.Name) {
133 | return true
134 | }
135 |
136 | jtag := f.Tag.Get("json")
137 | if len(jtag) == 0 {
138 | return false
139 | }
140 |
141 | return strings.Split(jtag, ",")[0] == "-"
142 | }
143 |
144 | type Visitor struct {
145 | Root string
146 | Methods map[string]ast.Node
147 | }
148 |
149 | func (v *Visitor) Visit(node ast.Node) ast.Visitor {
150 | st, ok := node.(*ast.TypeSpec)
151 | if !ok {
152 | return v
153 | }
154 |
155 | if st.Name.Name != v.Root {
156 | return nil
157 | }
158 |
159 | iface := st.Type.(*ast.InterfaceType)
160 | for _, m := range iface.Methods.List {
161 | if len(m.Names) > 0 {
162 | v.Methods[m.Names[0].Name] = m
163 | }
164 | }
165 |
166 | return v
167 | }
168 |
169 | const NoComment = "There are not yet any comments for this method."
170 |
171 | func ParseApiASTInfo() (comments map[string]string, groupDocs map[string]string) { //nolint:golint
172 | fset := token.NewFileSet()
173 | apiDir, err := filepath.Abs("api")
174 | if err != nil {
175 | fmt.Println("filepath absolute error: ", err)
176 | return
177 | }
178 | apiFile, err := filepath.Abs("api/api_miner.go")
179 | if err != nil {
180 | fmt.Println("filepath absolute error: ", err, "file:", apiFile)
181 | return
182 | }
183 | pkgs, err := parser.ParseDir(fset, apiDir, nil, parser.AllErrors|parser.ParseComments)
184 | if err != nil {
185 | fmt.Println("parse error: ", err)
186 | return
187 | }
188 |
189 | ap := pkgs["api"]
190 | f := ap.Files[apiFile]
191 | cmap := ast.NewCommentMap(fset, f, f.Comments)
192 |
193 | v := &Visitor{"MinerAPI", make(map[string]ast.Node)}
194 | ast.Walk(v, ap)
195 |
196 | comments = make(map[string]string)
197 | groupDocs = make(map[string]string)
198 | for mn, node := range v.Methods {
199 | filteredComments := cmap.Filter(node).Comments()
200 | if len(filteredComments) == 0 {
201 | comments[mn] = NoComment
202 | } else {
203 | for _, c := range filteredComments {
204 | if strings.HasPrefix(c.Text(), "MethodGroup:") {
205 | parts := strings.Split(c.Text(), "\n")
206 | groupName := strings.TrimSpace(parts[0][12:])
207 | comment := strings.Join(parts[1:], "\n")
208 | groupDocs[groupName] = comment
209 |
210 | break
211 | }
212 | }
213 |
214 | l := len(filteredComments) - 1
215 | if len(filteredComments) > 1 {
216 | l = len(filteredComments) - 2
217 | }
218 | last := filteredComments[l].Text()
219 | if !strings.HasPrefix(last, "MethodGroup:") {
220 | comments[mn] = last
221 | } else {
222 | comments[mn] = NoComment
223 | }
224 | }
225 | }
226 | return comments, groupDocs
227 | }
228 |
229 | type MethodGroup struct {
230 | GroupName string
231 | Header string
232 | Methods []*Method
233 | }
234 |
235 | type Method struct {
236 | Comment string
237 | Name string
238 | InputExample string
239 | ResponseExample string
240 | }
241 |
242 | func MethodGroupFromName(mn string) string {
243 | i := strings.IndexFunc(mn[1:], func(r rune) bool {
244 | return unicode.IsUpper(r)
245 | })
246 | if i < 0 {
247 | return ""
248 | }
249 | return mn[:i+1]
250 | }
251 |
--------------------------------------------------------------------------------
/api/docgen/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "reflect"
7 | "sort"
8 | "strings"
9 | )
10 |
11 | func main() {
12 | comments, groupComments := ParseApiASTInfo()
13 |
14 | groups := make(map[string]*MethodGroup)
15 |
16 | _, t, permStruct := GetAPIType()
17 |
18 | for i := 0; i < t.NumMethod(); i++ {
19 | m := t.Method(i)
20 |
21 | groupName := MethodGroupFromName(m.Name)
22 |
23 | g, ok := groups[groupName]
24 | if !ok {
25 | g = new(MethodGroup)
26 | g.Header = groupComments[groupName]
27 | g.GroupName = groupName
28 | groups[groupName] = g
29 | }
30 |
31 | var args []interface{}
32 | ft := m.Func.Type()
33 | for j := 2; j < ft.NumIn(); j++ {
34 | inp := ft.In(j)
35 | args = append(args, ExampleValue(m.Name, inp, nil))
36 | }
37 |
38 | v, err := json.MarshalIndent(args, "", " ")
39 | if err != nil {
40 | panic(err)
41 | }
42 |
43 | outv := ExampleValue(m.Name, ft.Out(0), nil)
44 |
45 | ov, err := json.MarshalIndent(outv, "", " ")
46 | if err != nil {
47 | panic(err)
48 | }
49 |
50 | g.Methods = append(g.Methods, &Method{
51 | Name: m.Name,
52 | Comment: comments[m.Name],
53 | InputExample: string(v),
54 | ResponseExample: string(ov),
55 | })
56 | }
57 |
58 | var groupslice []*MethodGroup
59 | for _, g := range groups {
60 | groupslice = append(groupslice, g)
61 | }
62 |
63 | sort.Slice(groupslice, func(i, j int) bool {
64 | return groupslice[i].GroupName < groupslice[j].GroupName
65 | })
66 |
67 | fmt.Printf("# Groups\n")
68 |
69 | for _, g := range groupslice {
70 | fmt.Printf("* [%s](#%s)\n", g.GroupName, g.GroupName)
71 | for _, method := range g.Methods {
72 | fmt.Printf(" * [%s](#%s)\n", method.Name, method.Name)
73 | }
74 | }
75 |
76 | for _, g := range groupslice {
77 | g := g
78 | fmt.Printf("## %s\n", g.GroupName)
79 | fmt.Printf("%s\n\n", g.Header)
80 |
81 | sort.Slice(g.Methods, func(i, j int) bool {
82 | return g.Methods[i].Name < g.Methods[j].Name
83 | })
84 |
85 | for _, m := range g.Methods {
86 | fmt.Printf("### %s\n", m.Name)
87 | fmt.Printf("%s\n\n", m.Comment)
88 |
89 | var meth reflect.StructField
90 | var ok bool
91 | for _, ps := range permStruct {
92 | meth, ok = ps.FieldByName(m.Name)
93 | if ok {
94 | break
95 | }
96 | }
97 | if !ok {
98 | panic("no perms for method: " + m.Name)
99 | }
100 |
101 | perms := meth.Tag.Get("perm")
102 |
103 | fmt.Printf("Perms: %s\n\n", perms)
104 |
105 | if strings.Count(m.InputExample, "\n") > 0 {
106 | fmt.Printf("Inputs:\n```json\n%s\n```\n\n", m.InputExample)
107 | } else {
108 | fmt.Printf("Inputs: `%s`\n\n", m.InputExample)
109 | }
110 |
111 | if strings.Count(m.ResponseExample, "\n") > 0 {
112 | fmt.Printf("Response:\n```json\n%s\n```\n\n", m.ResponseExample)
113 | } else {
114 | fmt.Printf("Response: `%s`\n\n", m.ResponseExample)
115 | }
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/build/clock.go:
--------------------------------------------------------------------------------
1 | package build
2 |
3 | import "github.com/raulk/clock"
4 |
5 | // Clock is the global clock for the system. In standard builds,
6 | // we use a real-time clock, which maps to the `time` package.
7 | //
8 | // Tests that need control of time can replace this variable with
9 | // clock.NewMock(). Always use real time for socket/stream deadlines.
10 | var Clock = clock.New()
11 |
--------------------------------------------------------------------------------
/build/tools.go:
--------------------------------------------------------------------------------
1 | //go:build tools
2 | // +build tools
3 |
4 | package build
5 |
6 | // in order to gen check: ci
7 | import (
8 | _ "github.com/golang/mock/mockgen"
9 | _ "golang.org/x/tools/cmd/stringer"
10 | )
11 |
--------------------------------------------------------------------------------
/build/version.go:
--------------------------------------------------------------------------------
1 | package build
2 |
3 | var CurrentCommit string
4 |
5 | // BuildVersion is the local build version, set by build system
6 | const BuildVersion = "1.18.0"
7 | const Version = "1180"
8 |
9 | func UserVersion() string {
10 | return BuildVersion + CurrentCommit
11 | }
12 |
--------------------------------------------------------------------------------
/cli/cmd.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "os"
8 | "os/signal"
9 | "syscall"
10 |
11 | logging "github.com/ipfs/go-log/v2"
12 | "github.com/mitchellh/go-homedir"
13 | "github.com/urfave/cli/v2"
14 |
15 | "github.com/filecoin-project/go-jsonrpc"
16 |
17 | "github.com/ipfs-force-community/sophon-miner/api"
18 | "github.com/ipfs-force-community/sophon-miner/api/client"
19 | "github.com/ipfs-force-community/sophon-miner/node/config"
20 | "github.com/ipfs-force-community/sophon-miner/node/repo"
21 |
22 | v1 "github.com/filecoin-project/venus/venus-shared/api/chain/v1"
23 | )
24 |
25 | var log = logging.Logger("cli")
26 |
27 | const (
28 | metadataTraceContext = "traceContext"
29 | )
30 |
31 | func GetAPIInfo(ctx *cli.Context) (config.APIInfo, error) {
32 | p, err := homedir.Expand(ctx.String("repo"))
33 | if err != nil {
34 | return config.APIInfo{}, fmt.Errorf("could not expand home dir: %w", err)
35 | }
36 |
37 | r, err := repo.NewFS(p)
38 | if err != nil {
39 | return config.APIInfo{}, fmt.Errorf("could not open repo at path: %s; %w", p, err)
40 | }
41 |
42 | // todo: rm compatibility for repo when appropriate
43 | exist, _ := r.Exists()
44 | if !exist {
45 | r, err = repo.NewFS("~/.venusminer")
46 | if err != nil {
47 | return config.APIInfo{}, err
48 | }
49 | }
50 |
51 | ma, err := r.APIEndpoint()
52 | if err != nil {
53 | return config.APIInfo{}, fmt.Errorf("could not get api endpoint: %w", err)
54 | }
55 |
56 | token, err := r.APIToken()
57 | if err != nil {
58 | log.Warnf("Couldn't load CLI token, capabilities may be limited: %v", err)
59 | }
60 |
61 | return config.APIInfo{
62 | Addr: ma.String(),
63 | Token: string(token),
64 | }, nil
65 | }
66 |
67 | func GetRawAPI(ctx *cli.Context, version string) (string, http.Header, error) {
68 | ainfo, err := GetAPIInfo(ctx)
69 | if err != nil {
70 | return "", nil, fmt.Errorf("could not get API info: %w", err)
71 | }
72 |
73 | addr, err := ainfo.DialArgs(version)
74 | if err != nil {
75 | return "", nil, fmt.Errorf("could not get DialArgs: %w", err)
76 | }
77 |
78 | return addr, ainfo.AuthHeader(), nil
79 | }
80 |
81 | func GetMinerAPI(ctx *cli.Context) (api.MinerAPI, jsonrpc.ClientCloser, error) {
82 | addr, headers, err := GetRawAPI(ctx, "v0")
83 | if err != nil {
84 | return nil, nil, err
85 | }
86 |
87 | return client.NewMinerRPC(ctx.Context, addr, headers)
88 | }
89 |
90 | func GetFullNodeAPI(ctx *cli.Context, fn *config.APIInfo, version string) (v1.FullNode, jsonrpc.ClientCloser, error) {
91 | addr, err := fn.DialArgs(version)
92 | if err != nil {
93 | return nil, nil, fmt.Errorf("could not get DialArgs: %w", err)
94 | }
95 |
96 | return v1.NewFullNodeRPC(ctx.Context, addr, fn.AuthHeader())
97 | }
98 |
99 | func DaemonContext(cctx *cli.Context) context.Context {
100 | if mtCtx, ok := cctx.App.Metadata[metadataTraceContext]; ok {
101 | return mtCtx.(context.Context)
102 | }
103 |
104 | return context.Background()
105 | }
106 |
107 | // ReqContext returns context for cli execution. Calling it for the first time
108 | // installs SIGTERM handler that will close returned context.
109 | // Not safe for concurrent execution.
110 | func ReqContext(cctx *cli.Context) context.Context {
111 | tCtx := DaemonContext(cctx)
112 |
113 | ctx, done := context.WithCancel(tCtx)
114 | sigChan := make(chan os.Signal, 2)
115 | go func() {
116 | <-sigChan
117 | done()
118 | }()
119 | signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
120 |
121 | return ctx
122 | }
123 |
124 | var CommonCommands = []*cli.Command{
125 | logCmd,
126 | VersionCmd,
127 | }
128 |
--------------------------------------------------------------------------------
/cli/helper.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "os"
7 |
8 | ufcli "github.com/urfave/cli/v2"
9 | )
10 |
11 | type PrintHelpErr struct {
12 | Err error
13 | Ctx *ufcli.Context
14 | }
15 |
16 | func (e *PrintHelpErr) Error() string {
17 | return e.Err.Error()
18 | }
19 |
20 | func (e *PrintHelpErr) Unwrap() error {
21 | return e.Err
22 | }
23 |
24 | func (e *PrintHelpErr) Is(o error) bool {
25 | _, ok := o.(*PrintHelpErr)
26 | return ok
27 | }
28 |
29 | func RunApp(app *ufcli.App) {
30 | if err := app.Run(os.Args); err != nil {
31 | fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err) // nolint:errcheck
32 | var phe *PrintHelpErr
33 | if errors.As(err, &phe) {
34 | _ = ufcli.ShowCommandHelp(phe.Ctx, phe.Ctx.Command.Name)
35 | }
36 | os.Exit(1)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/cli/log.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/urfave/cli/v2"
7 | )
8 |
9 | var logCmd = &cli.Command{
10 | Name: "log",
11 | Usage: "Module log level management",
12 | Subcommands: []*cli.Command{
13 | logList,
14 | logSetLevel,
15 | },
16 | }
17 |
18 | var logList = &cli.Command{
19 | Name: "list",
20 | Usage: "List log systems",
21 | Action: func(cctx *cli.Context) error {
22 | api, closer, err := GetMinerAPI(cctx)
23 | if err != nil {
24 | return err
25 | }
26 | defer closer()
27 |
28 | ctx := ReqContext(cctx)
29 |
30 | systems, err := api.LogList(ctx)
31 | if err != nil {
32 | return err
33 | }
34 |
35 | for _, system := range systems {
36 | fmt.Println(system)
37 | }
38 |
39 | return nil
40 | },
41 | }
42 |
43 | var logSetLevel = &cli.Command{
44 | Name: "set-level",
45 | Usage: "Set log level",
46 | ArgsUsage: "[level]",
47 | Description: `Set the log level for logging systems:
48 |
49 | The system flag can be specified multiple times.
50 |
51 | eg) log set-level --system chain --system chainxchg debug
52 |
53 | Available Levels:
54 | debug
55 | info
56 | warn
57 | error
58 |
59 | Environment Variables:
60 | GOLOG_LOG_LEVEL - Default log level for all log systems
61 | GOLOG_LOG_FMT - Change output log format (json, nocolor)
62 | GOLOG_FILE - Write logs to file
63 | GOLOG_OUTPUT - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr
64 | `,
65 | Flags: []cli.Flag{
66 | &cli.StringSliceFlag{
67 | Name: "system",
68 | Usage: "limit to log system",
69 | Value: &cli.StringSlice{},
70 | },
71 | },
72 | Action: func(cctx *cli.Context) error {
73 | api, closer, err := GetMinerAPI(cctx)
74 | if err != nil {
75 | return err
76 | }
77 | defer closer()
78 | ctx := ReqContext(cctx)
79 |
80 | if !cctx.Args().Present() {
81 | return fmt.Errorf("level is required")
82 | }
83 |
84 | systems := cctx.StringSlice("system")
85 | if len(systems) == 0 {
86 | var err error
87 | systems, err = api.LogList(ctx)
88 | if err != nil {
89 | return err
90 | }
91 | }
92 |
93 | for _, system := range systems {
94 | if err := api.LogSetLevel(ctx, system, cctx.Args().First()); err != nil {
95 | return fmt.Errorf("setting log level on %s: %w", system, err)
96 | }
97 | }
98 |
99 | return nil
100 | },
101 | }
102 |
--------------------------------------------------------------------------------
/cli/version.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/urfave/cli/v2"
7 | )
8 |
9 | var VersionCmd = &cli.Command{
10 | Name: "version",
11 | Usage: "Print version",
12 | Action: func(cctx *cli.Context) error {
13 | api, closer, err := GetMinerAPI(cctx)
14 | if err != nil {
15 | return err
16 | }
17 | defer closer()
18 |
19 | ctx := ReqContext(cctx)
20 | v, err := api.Version(ctx)
21 | if err != nil {
22 | return err
23 | }
24 | fmt.Println("Daemon: ", v)
25 |
26 | fmt.Print("Local: ")
27 | cli.VersionPrinter(cctx)
28 | return nil
29 | },
30 | }
31 |
--------------------------------------------------------------------------------
/cmd/address.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/urfave/cli/v2"
8 |
9 | "github.com/filecoin-project/go-address"
10 | lcli "github.com/ipfs-force-community/sophon-miner/cli"
11 | )
12 |
13 | var addressCmd = &cli.Command{
14 | Name: "address",
15 | Usage: "manage the miner address",
16 | Subcommands: []*cli.Command{
17 | updateCmd,
18 | listCmd,
19 | stateCmd,
20 | startMiningCmd,
21 | stopMiningCmd,
22 | warmupCmd,
23 | },
24 | }
25 |
26 | var updateCmd = &cli.Command{
27 | Name: "update",
28 | Usage: "reacquire address from sophon-auth",
29 | Flags: []cli.Flag{
30 | &cli.Int64Flag{
31 | Name: "skip",
32 | Required: false,
33 | },
34 | &cli.Int64Flag{
35 | Name: "limit",
36 | Required: false,
37 | },
38 | },
39 | Action: func(cctx *cli.Context) error {
40 | postApi, closer, err := lcli.GetMinerAPI(cctx)
41 | if err != nil {
42 | return err
43 | }
44 | defer closer()
45 |
46 | skip := cctx.Int64("skip")
47 | limit := cctx.Int64("limit")
48 |
49 | miners, err := postApi.UpdateAddress(cctx.Context, skip, limit)
50 | if err != nil {
51 | return err
52 | }
53 |
54 | formatJson, err := json.MarshalIndent(miners, "", "\t")
55 | if err != nil {
56 | return err
57 | }
58 | fmt.Println(string(formatJson))
59 |
60 | return nil
61 | },
62 | }
63 |
64 | var listCmd = &cli.Command{
65 | Name: "list",
66 | Usage: "print miners",
67 | Flags: []cli.Flag{},
68 | Action: func(cctx *cli.Context) error {
69 | postApi, closer, err := lcli.GetMinerAPI(cctx)
70 | if err != nil {
71 | return err
72 | }
73 | defer closer()
74 |
75 | miners, err := postApi.ListAddress(cctx.Context)
76 | if err != nil {
77 | return err
78 | }
79 |
80 | formatJson, err := json.MarshalIndent(miners, "", "\t")
81 | if err != nil {
82 | return err
83 | }
84 | fmt.Println(string(formatJson))
85 | return nil
86 |
87 | },
88 | }
89 |
90 | var stateCmd = &cli.Command{
91 | Name: "state",
92 | Usage: "print state of mining",
93 | Flags: []cli.Flag{},
94 | ArgsUsage: "[address ...]",
95 | Action: func(cctx *cli.Context) error {
96 | postApi, closer, err := lcli.GetMinerAPI(cctx)
97 | if err != nil {
98 | return err
99 | }
100 | defer closer()
101 |
102 | var addrs []address.Address
103 | for i, s := range cctx.Args().Slice() {
104 | minerAddr, err := address.NewFromString(s)
105 | if err != nil {
106 | return fmt.Errorf("parsing %d-th miner: %w", i, err)
107 | }
108 |
109 | addrs = append(addrs, minerAddr)
110 | }
111 |
112 | states, err := postApi.StatesForMining(cctx.Context, addrs)
113 | if err != nil {
114 | return err
115 | }
116 |
117 | formatJson, err := json.MarshalIndent(states, "", "\t")
118 | if err != nil {
119 | return err
120 | }
121 | fmt.Println(string(formatJson))
122 | return nil
123 |
124 | },
125 | }
126 |
127 | var startMiningCmd = &cli.Command{
128 | Name: "start",
129 | Usage: "start mining for specified miner, if not specified, it means all",
130 | Flags: []cli.Flag{},
131 | ArgsUsage: "[address ...]",
132 | Action: func(cctx *cli.Context) error {
133 | postApi, closer, err := lcli.GetMinerAPI(cctx)
134 | if err != nil {
135 | return err
136 | }
137 | defer closer()
138 | ctx := lcli.ReqContext(cctx)
139 |
140 | var addrs []address.Address
141 | for i, s := range cctx.Args().Slice() {
142 | minerAddr, err := address.NewFromString(s)
143 | if err != nil {
144 | return fmt.Errorf("parsing %d-th miner: %w", i, err)
145 | }
146 |
147 | addrs = append(addrs, minerAddr)
148 | }
149 |
150 | err = postApi.Start(ctx, addrs)
151 | if err != nil {
152 | return err
153 | }
154 |
155 | fmt.Println("start mining success.")
156 | return nil
157 | },
158 | }
159 |
160 | var stopMiningCmd = &cli.Command{
161 | Name: "stop",
162 | Usage: "stop mining for specified miner, if not specified, it means all",
163 | Flags: []cli.Flag{},
164 | ArgsUsage: "[address ...]",
165 | Action: func(cctx *cli.Context) error {
166 | postApi, closer, err := lcli.GetMinerAPI(cctx)
167 | if err != nil {
168 | return err
169 | }
170 | defer closer()
171 | ctx := lcli.ReqContext(cctx)
172 |
173 | var addrs []address.Address
174 | for i, s := range cctx.Args().Slice() {
175 | minerAddr, err := address.NewFromString(s)
176 | if err != nil {
177 | return fmt.Errorf("parsing %d-th miner: %w", i, err)
178 | }
179 |
180 | addrs = append(addrs, minerAddr)
181 | }
182 |
183 | err = postApi.Stop(ctx, addrs)
184 | if err != nil {
185 | return err
186 | }
187 |
188 | fmt.Println("stop mining success.")
189 | return nil
190 | },
191 | }
192 |
193 | var warmupCmd = &cli.Command{
194 | Name: "warmup",
195 | Usage: "winPoSt warmup for miner",
196 | Flags: []cli.Flag{},
197 | ArgsUsage: "",
198 | Action: func(cctx *cli.Context) error {
199 | if count := cctx.Args().Len(); count < 1 {
200 | return cli.ShowSubcommandHelp(cctx)
201 | }
202 |
203 | minerApi, closer, err := lcli.GetMinerAPI(cctx)
204 | if err != nil {
205 | return err
206 | }
207 | defer closer()
208 |
209 | minerAddr, err := address.NewFromString(cctx.Args().First())
210 | if err != nil {
211 | return fmt.Errorf("parsing miner: %w", err)
212 | }
213 |
214 | err = minerApi.WarmupForMiner(cctx.Context, minerAddr)
215 | if err == nil {
216 | fmt.Println("warmup success.")
217 | } else {
218 | fmt.Println("warmup failed: ", err.Error())
219 | }
220 |
221 | return nil
222 | },
223 | }
224 |
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 |
6 | logging "github.com/ipfs/go-log/v2"
7 | "github.com/urfave/cli/v2"
8 | "go.opencensus.io/trace"
9 |
10 | "github.com/ipfs-force-community/sophon-miner/build"
11 | lcli "github.com/ipfs-force-community/sophon-miner/cli"
12 | "github.com/ipfs-force-community/sophon-miner/lib/logger"
13 | )
14 |
15 | var log = logging.Logger("main")
16 |
17 | const FlagMinerRepo = "repo"
18 |
19 | func main() {
20 | logger.SetupLogLevels()
21 |
22 | local := []*cli.Command{
23 | initCmd,
24 | runCmd,
25 | stopCmd,
26 | addressCmd,
27 | winnerCmd,
28 | recordCmd,
29 | }
30 |
31 | ctx, span := trace.StartSpan(context.Background(), "/cli")
32 | defer span.End()
33 |
34 | app := &cli.App{
35 | Name: "sophon-miner",
36 | Usage: "Filecoin decentralized storage network miner",
37 | Version: build.UserVersion(),
38 | EnableBashCompletion: true,
39 | Flags: []cli.Flag{
40 | &cli.StringFlag{
41 | Name: FlagMinerRepo,
42 | EnvVars: []string{
43 | "SOPHON_MINER_PATH",
44 | "VENUS_MINER_PATH", // TODO Deprecated after V1.13.*
45 | },
46 | Aliases: []string{"miner-repo"},
47 | Value: "~/.sophon-miner",
48 | Usage: "Specify miner repo path, env SOPHON_MINER_PATH",
49 | },
50 | },
51 |
52 | Commands: append(local, lcli.CommonCommands...),
53 | }
54 | app.Setup()
55 | app.Metadata["traceContext"] = ctx
56 |
57 | lcli.RunApp(app)
58 | }
59 |
--------------------------------------------------------------------------------
/cmd/record.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strconv"
7 |
8 | "github.com/filecoin-project/go-address"
9 | "github.com/filecoin-project/go-state-types/abi"
10 | lcli "github.com/ipfs-force-community/sophon-miner/cli"
11 | "github.com/ipfs-force-community/sophon-miner/types"
12 | "github.com/urfave/cli/v2"
13 | )
14 |
15 | var recordCmd = &cli.Command{
16 | Name: "record",
17 | Usage: "the record about mine block",
18 | Subcommands: []*cli.Command{
19 | queryCmd,
20 | },
21 | }
22 |
23 | var queryCmd = &cli.Command{
24 | Name: "query",
25 | Usage: "query record",
26 | ArgsUsage: " ",
27 | Flags: []cli.Flag{
28 | &cli.UintFlag{
29 | Name: "limit",
30 | Usage: "query nums record of limit",
31 | Value: 1,
32 | },
33 | },
34 | Action: func(cctx *cli.Context) error {
35 | if cctx.NArg() != 2 {
36 | return cli.ShowSubcommandHelp(cctx)
37 | }
38 |
39 | minerAddr, err := address.NewFromString(cctx.Args().Get(0))
40 | if err != nil {
41 | return err
42 | }
43 |
44 | epoch, err := strconv.ParseUint(cctx.Args().Get(1), 10, 64)
45 | if err != nil {
46 | return err
47 | }
48 |
49 | minerAPI, closer, err := lcli.GetMinerAPI(cctx)
50 | if err != nil {
51 | return err
52 | }
53 | defer closer()
54 |
55 | res, err := minerAPI.QueryRecord(lcli.ReqContext(cctx), &types.QueryRecordParams{
56 | Epoch: abi.ChainEpoch(epoch),
57 | Limit: cctx.Uint("limit"),
58 | Miner: minerAddr,
59 | })
60 | if err != nil {
61 | return err
62 | }
63 |
64 | formatJson, err := json.MarshalIndent(res, "", "\t")
65 | if err != nil {
66 | return err
67 | }
68 |
69 | fmt.Println(string(formatJson))
70 | return nil
71 | },
72 | }
73 |
--------------------------------------------------------------------------------
/cmd/stop.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | _ "net/http/pprof"
5 |
6 | "github.com/urfave/cli/v2"
7 |
8 | lcli "github.com/ipfs-force-community/sophon-miner/cli"
9 | )
10 |
11 | var stopCmd = &cli.Command{
12 | Name: "stop",
13 | Usage: "Stop running sophon-miner daemon",
14 | Flags: []cli.Flag{},
15 | Action: func(cctx *cli.Context) error {
16 | api, closer, err := lcli.GetMinerAPI(cctx)
17 | if err != nil {
18 | return err
19 | }
20 | defer closer()
21 |
22 | err = api.Shutdown(lcli.ReqContext(cctx))
23 | if err != nil {
24 | return err
25 | }
26 |
27 | return nil
28 | },
29 | }
30 |
--------------------------------------------------------------------------------
/cmd/winner.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 |
7 | "github.com/filecoin-project/go-address"
8 | "github.com/filecoin-project/go-state-types/abi"
9 | "github.com/urfave/cli/v2"
10 |
11 | lcli "github.com/ipfs-force-community/sophon-miner/cli"
12 | )
13 |
14 | var winnerCmd = &cli.Command{
15 | Name: "winner",
16 | Usage: "block right management",
17 | Subcommands: []*cli.Command{
18 | countCmd,
19 | },
20 | }
21 |
22 | var countCmd = &cli.Command{
23 | Name: "count",
24 | Usage: "Count the block rights of the specified miner",
25 | Flags: []cli.Flag{
26 | &cli.Int64Flag{
27 | Name: "epoch-start",
28 | Required: true,
29 | },
30 | &cli.Int64Flag{
31 | Name: "epoch-end",
32 | Required: true,
33 | },
34 | },
35 | ArgsUsage: "[address ...]",
36 | Action: func(cctx *cli.Context) error {
37 | minerAPI, closer, err := lcli.GetMinerAPI(cctx)
38 | if err != nil {
39 | return err
40 | }
41 | defer closer()
42 |
43 | var addrs []address.Address
44 | for i, s := range cctx.Args().Slice() {
45 | minerAddr, err := address.NewFromString(s)
46 | if err != nil {
47 | return fmt.Errorf("parsing %d-th miner: %w", i, err)
48 | }
49 |
50 | addrs = append(addrs, minerAddr)
51 | }
52 |
53 | epochStart := cctx.Int64("epoch-start")
54 | epochEnd := cctx.Int64("epoch-end")
55 |
56 | winners, err := minerAPI.CountWinners(cctx.Context, addrs, abi.ChainEpoch(epochStart), abi.ChainEpoch(epochEnd))
57 | if err != nil {
58 | return err
59 | }
60 |
61 | formatJson, err := json.MarshalIndent(winners, "", "\t")
62 | if err != nil {
63 | return err
64 | }
65 | fmt.Println(string(formatJson))
66 | return nil
67 |
68 | },
69 | }
70 |
--------------------------------------------------------------------------------
/docs/en/api-v0-methods-miner.md:
--------------------------------------------------------------------------------
1 | # Groups
2 | * [](#)
3 | * [Closing](#Closing)
4 | * [Session](#Session)
5 | * [Shutdown](#Shutdown)
6 | * [Start](#Start)
7 | * [Stop](#Stop)
8 | * [Version](#Version)
9 | * [Count](#Count)
10 | * [CountWinners](#CountWinners)
11 | * [List](#List)
12 | * [ListAddress](#ListAddress)
13 | * [ListBlocks](#ListBlocks)
14 | * [Log](#Log)
15 | * [LogList](#LogList)
16 | * [LogSetLevel](#LogSetLevel)
17 | * [Query](#Query)
18 | * [QueryRecord](#QueryRecord)
19 | * [States](#States)
20 | * [StatesForMining](#StatesForMining)
21 | * [Update](#Update)
22 | * [UpdateAddress](#UpdateAddress)
23 | * [Warmup](#Warmup)
24 | * [WarmupForMiner](#WarmupForMiner)
25 | ##
26 |
27 |
28 | ### Closing
29 |
30 |
31 | Perms: read
32 |
33 | Inputs: `null`
34 |
35 | Response: `{}`
36 |
37 | ### Session
38 |
39 |
40 | Perms: read
41 |
42 | Inputs: `null`
43 |
44 | Response: `"07070707-0707-0707-0707-070707070707"`
45 |
46 | ### Shutdown
47 |
48 |
49 | Perms: admin
50 |
51 | Inputs: `null`
52 |
53 | Response: `{}`
54 |
55 | ### Start
56 |
57 |
58 | Perms: admin
59 |
60 | Inputs:
61 | ```json
62 | [
63 | [
64 | "t01234"
65 | ]
66 | ]
67 | ```
68 |
69 | Response: `{}`
70 |
71 | ### Stop
72 |
73 |
74 | Perms: admin
75 |
76 | Inputs:
77 | ```json
78 | [
79 | [
80 | "t01234"
81 | ]
82 | ]
83 | ```
84 |
85 | Response: `{}`
86 |
87 | ### Version
88 |
89 |
90 | Perms: read
91 |
92 | Inputs: `null`
93 |
94 | Response:
95 | ```json
96 | {
97 | "Version": "string value",
98 | "APIVersion": 0
99 | }
100 | ```
101 |
102 | ## Count
103 |
104 |
105 | ### CountWinners
106 |
107 |
108 | Perms: read
109 |
110 | Inputs:
111 | ```json
112 | [
113 | [
114 | "t01234"
115 | ],
116 | 10101,
117 | 10101
118 | ]
119 | ```
120 |
121 | Response:
122 | ```json
123 | [
124 | {
125 | "miner": "t01234",
126 | "totalWinCount": 0,
127 | "msg": "string value",
128 | "winEpochList": null
129 | }
130 | ]
131 | ```
132 |
133 | ## List
134 |
135 |
136 | ### ListAddress
137 |
138 |
139 | Perms: read
140 |
141 | Inputs: `null`
142 |
143 | Response:
144 | ```json
145 | [
146 | {
147 | "Addr": "t01234",
148 | "Id": "string value",
149 | "Name": "string value",
150 | "OpenMining": false
151 | }
152 | ]
153 | ```
154 |
155 | ### ListBlocks
156 |
157 |
158 | Perms: read
159 |
160 | Inputs:
161 | ```json
162 | [
163 | {
164 | "Miners": [
165 | "t01234"
166 | ],
167 | "Limit": 42,
168 | "Offset": 42
169 | }
170 | ]
171 | ```
172 |
173 | Response:
174 | ```json
175 | [
176 | {
177 | "ParentEpoch": 0,
178 | "ParentKey": "",
179 | "Epoch": 9,
180 | "Miner": "string value",
181 | "Cid": "string value",
182 | "WinningAt": "0001-01-01T00:00:00Z",
183 | "MineState": 0,
184 | "Consuming": 9
185 | }
186 | ]
187 | ```
188 |
189 | ## Log
190 |
191 |
192 | ### LogList
193 |
194 |
195 | Perms: write
196 |
197 | Inputs: `null`
198 |
199 | Response:
200 | ```json
201 | [
202 | "string value"
203 | ]
204 | ```
205 |
206 | ### LogSetLevel
207 |
208 |
209 | Perms: write
210 |
211 | Inputs:
212 | ```json
213 | [
214 | "string value",
215 | "string value"
216 | ]
217 | ```
218 |
219 | Response: `{}`
220 |
221 | ## Query
222 |
223 |
224 | ### QueryRecord
225 |
226 |
227 | Perms: read
228 |
229 | Inputs:
230 | ```json
231 | [
232 | {
233 | "Miner": "t01234",
234 | "Epoch": 10101,
235 | "Limit": 42
236 | }
237 | ]
238 | ```
239 |
240 | Response:
241 | ```json
242 | [
243 | {}
244 | ]
245 | ```
246 |
247 | ## States
248 |
249 |
250 | ### StatesForMining
251 |
252 |
253 | Perms: read
254 |
255 | Inputs:
256 | ```json
257 | [
258 | [
259 | "t01234"
260 | ]
261 | ]
262 | ```
263 |
264 | Response:
265 | ```json
266 | [
267 | {
268 | "Addr": "t01234",
269 | "IsMining": false,
270 | "Err": [
271 | "string value"
272 | ]
273 | }
274 | ]
275 | ```
276 |
277 | ## Update
278 |
279 |
280 | ### UpdateAddress
281 |
282 |
283 | Perms: admin
284 |
285 | Inputs:
286 | ```json
287 | [
288 | 9,
289 | 9
290 | ]
291 | ```
292 |
293 | Response:
294 | ```json
295 | [
296 | {
297 | "Addr": "t01234",
298 | "Id": "string value",
299 | "Name": "string value",
300 | "OpenMining": false
301 | }
302 | ]
303 | ```
304 |
305 | ## Warmup
306 |
307 |
308 | ### WarmupForMiner
309 |
310 |
311 | Perms: write
312 |
313 | Inputs:
314 | ```json
315 | [
316 | "t01234"
317 | ]
318 | ```
319 |
320 | Response: `{}`
321 |
322 |
--------------------------------------------------------------------------------
/docs/en/config-desc.md:
--------------------------------------------------------------------------------
1 | # Configuration parsing
2 |
3 | The file is located in `~/.sophon-miner/config.toml` by default, which is generated when the command `sophon-miner init` is executed. Behavior comments starting with `#` in the file.
4 |
5 |
6 | ## Old version
7 |
8 | version number `< v1.7.0`
9 |
10 | ```toml
11 | # venus fullnode API
12 | ListenAPI = "/ip4/127.0.0.1/tcp/3453"
13 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.RRSdeQ-c1Ei-8roAj-L-wpOr-y6PssDorbGijMPxjoc"
14 | # deprecated, replaced by `slash filter`
15 | BlockRecord = "cache"
16 |
17 | [API]
18 | ListenAddress = "/ip4/0.0.0.0/tcp/12308/http"
19 |
20 | # venus-gateway API
21 | [Gateway]
22 | ListenAPI = ["/ip4/127.0.0.1/tcp/45132"]
23 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.RRSdeQ-c1Ei-8roAj-L-wpOr-y6PssDorbGijMPxjoc"
24 |
25 | [Db]
26 | # deprecated, replaced by ~sophon-auth`
27 | Type = "auth"
28 | # `slash filter`
29 | SFType = "mysql"
30 | [Db.MySQL]
31 | Conn = "root:kuangfengjuexizhan@tcp(192.168.200.2:3308)/sophon-miner-butterfly-200-19?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s"
32 | MaxOpenConn = 100
33 | MaxIdleConn = 10
34 | ConnMaxLifeTime = 60
35 | Debug = false
36 | [Db.Auth]
37 | ListenAPI = "http://127.0.0.1:8989"
38 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.RRSdeQ-c1Ei-8roAj-L-wpOr-y6PssDorbGijMPxjoc"
39 |
40 | # Jaeger Tracing
41 | [Tracing]
42 | JaegerTracingEnabled = false
43 | JaegerEndpoint = "localhost:6831"
44 | ProbabilitySampler = 1.0
45 | ServerName = "sophon-miner"
46 | ```
47 |
48 | ## New version
49 |
50 | version number `>= v1.7.0`
51 |
52 | ```toml
53 | PropagationDelaySecs = 12
54 | MinerOnceTimeout = "15s"
55 | MpoolSelectDelaySecs = 0
56 |
57 | # `sophon-miner` host address and port
58 | [API]
59 | ListenAddress = "/ip4/0.0.0.0/tcp/12308"
60 |
61 | # venus fullnode API
62 | [FullNode]
63 | Addr = "/ip4/127.0.0.1/tcp/3453"
64 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2hhaW4tc2VydmljZSIsInBlcm0iOiJhZG1pbiIsImV4dCI6IiJ9.DxlsJO-XrrdQLvJdA6wdWJxeYOhZt_kMYMHc7NdfQNw"
65 |
66 | # venus-gateway API
67 | [Gateway]
68 | ListenAPI = ["/ip4/127.0.0.1/tcp/45132"]
69 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2hhaW4tc2VydmljZSIsInBlcm0iOiJhZG1pbiIsImV4dCI6IiJ9.DxlsJO-XrrdQLvJdA6wdWJxeYOhZt_kMYMHc7NdfQNw"
70 |
71 | # sophon-auth API
72 | [Auth]
73 | Addr = "http://127.0.0.1:8989"
74 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2hhaW4tc2VydmljZSIsInBlcm0iOiJhZG1pbiIsImV4dCI6IiJ9.DxlsJO-XrrdQLvJdA6wdWJxeYOhZt_kMYMHc7NdfQNw"
75 |
76 | # `slash filter`
77 | [SlashFilter]
78 | Type = "local" # optional: local,mysql
79 | [SlashFilter.MySQL]
80 | Conn = ""
81 | MaxOpenConn = 100
82 | MaxIdleConn = 10
83 | ConnMaxLifeTime = 60
84 | Debug = false
85 |
86 | # Jaeger Tracing
87 | [Tracing]
88 | JaegerTracingEnabled = false
89 | JaegerEndpoint = "localhost:6831"
90 | ProbabilitySampler = 1.0
91 | ServerName = "sophon-miner"
92 | ```
93 |
94 | ### Metrics Configuration parsing
95 |
96 | A basic configuration example of `Metrics` is as follows:
97 | ```toml
98 | [Metrics]
99 | # Whether to enable metrics statistics, the default is false
100 | Enabled = false
101 |
102 | [Metrics.Exporter]
103 | # Metric exporter type, currently optional: prometheus or graphite, default is prometheus
104 | Type = "prometheus"
105 |
106 | [Metrics.Exporter.Prometheus]
107 | #multiaddr
108 | EndPoint = "/ip4/0.0.0.0/tcp/12310"
109 | # Naming convention: "a_b_c", no "-"
110 | Namespace = "miner"
111 | # Indicator registry type, optional: default (with the environment indicators that the program runs) or define (custom)
112 | RegistryType = "define"
113 | # prometheus service path
114 | Path = "/debug/metrics"
115 | # Reporting period, in seconds (s)
116 | ReportingPeriod = 10
117 |
118 | [Metrics.Exporter.Graphite]
119 | # Naming convention: "a_b_c", no "-"
120 | Namespace = "miner"
121 | # graphite exporter collector service address
122 | Host = "127.0.0.1"
123 | # graphite exporter collector service listening port
124 | Port = 12310
125 | # Reporting period, in seconds (s)
126 | ReportingPeriod = 10
127 | ```
128 |
129 | If you choose `Metrics.Exporter` as `Prometheus`, you can quickly view metrics through the command line:
130 |
131 | ```
132 | $ curl http://127.0.0.1:
133 | # HELP miner_getbaseinfo_ms Duration of GetBaseInfo in miner
134 | # TYPE miner_getbaseinfo_ms histogram
135 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="100"} 50
136 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="200"} 51
137 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="400"} 51
138 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="600"} 51
139 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="800"} 51
140 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="1000"} 51
141 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="2000"} 51
142 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="20000"} 51
143 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="+Inf"} 51
144 | miner_getbaseinfo_ms_sum{miner_id="t010938"} 470.23516
145 | ... ...
146 | ```
147 | > If you encounter the error `curl: (56) Recv failure: Connection reset by peer`, use the native `ip` address as follows:
148 | ```
149 | $ curl http://:
150 | ```
151 |
--------------------------------------------------------------------------------
/docs/en/jaeger-tracing.md:
--------------------------------------------------------------------------------
1 | # Jaeger Tracing
2 |
3 | `sophon-miner` has tracing built into many of its internals. To view the traces, first download [Jaeger](https://www.jaegertracing.io/download/) (Choose the 'all-in-one' binary). Then run it somewhere, start up the sophon-miner daemon, and open up localhost:16686 in your browser.
4 |
5 | ## Open Census
6 |
7 | `sophon-miner` uses [OpenCensus](https://opencensus.io/) for tracing application flow. This generates spans through the execution of annotated code paths.
8 |
9 | Currently it is set up to use Jaeger, though other tracing backends should be fairly easy to swap in.
10 |
11 | ## Running Locally
12 |
13 | To easily run and view tracing locally, first, install jaeger. The easiest way to do this is to [download the binaries](https://www.jaegertracing.io/download/) and then run the `jaeger-all-in-one` binary. This will start up jaeger, listen for spans on `localhost:6831`, and expose a web UI for viewing traces on `http://localhost:16686/`.
14 |
15 | Now, to start sending traces from Venus-Miner to Jaeger, set the environment variable `SOPHON_MINER_JAEGER` to `localhost:6831`, and start the `sophon-miner run`.
16 |
17 | Now, to view any generated traces, open up `http://localhost:16686/` in your browser.
18 |
19 | ## Adding Spans
20 |
21 | To annotate a new codepath with spans, add the following lines to the top of the function you wish to trace:
22 |
23 | ```go
24 | ctx, span := trace.StartSpan(ctx, "put function name here")
25 | defer span.End()
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/en/metrics.md:
--------------------------------------------------------------------------------
1 | ## `sophon-miner` metrics
2 |
3 | For the configuration of enabling indicators and exporters, please refer to[metrics config](./config-desc.md)
4 |
5 | ```
6 | // Type: Histogram, Tag: minerID, Semantics: Time to get `BaseInfo` (unit: milliseconds)
7 | GetBaseInfoDuration = stats.Float64("getbaseinfo_ms", "Duration of GetBaseInfo in miner", stats.UnitMilliseconds)
8 | // Type: histogram, Tag: minerID, Semantics: time-consuming to calculate `ticket` (unit: milliseconds)
9 | ComputeTicketDuration = stats.Float64("computeticket_ms", "Duration of ComputeTicket in miner", stats.UnitMilliseconds)
10 | // Type: Histogram, Tag: minerID, Semantics: Time to calculate whether to win the block right (unit: milliseconds)
11 | IsRoundWinnerDuration = stats.Float64("isroundwinner_ms", "Duration of IsRoundWinner in miner", stats.UnitMilliseconds)
12 | // Type: Histogram, Tag: minerID, Semantics: Time to calculate winning proof (unit: seconds)
13 | ComputeProofDuration = stats.Float64("computeproof_s", "Duration of ComputeProof in miner", stats.UnitSeconds)
14 |
15 | // Type: Counter, Tag: minerID, Semantics: The number of successful blocks, which means that the local verification
16 | // passed and the broadcast was successful. It was not recognized or excluded due to insufficient base or consensus error.
17 | NumberOfBlock = stats.Int64("number_of_block", "Number of production blocks", stats.UnitDimensionless)
18 | // Type: Counter, Tag: minerID, Semantics: The number of times to obtain the right to produce blocks,
19 | // if the calculation is on the fork chain, the block production will not be recognized
20 | NumberOfIsRoundWinner = stats.Int64("number_of_isroundwinner", "Number of is round winner", stats.UnitDimensionless)
21 |
22 | // Type: Counter, Tag: minerID, Semantics: The number of times that the block failed due to the timeout of calculating the winning proof
23 | NumberOfMiningTimeout = stats.Int64("number_of_mining_timeout", "Number of mining failures due to compute proof timeout", stats.UnitDimensionless)
24 | // Type: Counter, Tag: minerID, Semantics: The number of times the block was abandoned due to the chain fork,
25 | // the block generation on the forked chain is meaningless, so the subsequent logic will be abandoned
26 | // if the chain fork is verified before the block is generated
27 | NumberOfMiningChainFork = stats.Int64("number_of_mining_chain_fork", "Number of mining failures due to chain fork", stats.UnitDimensionless)
28 | // Type: Counter, Tag: minerID, Semantics: Number of block errors due to other errors
29 | NumberOfMiningError = stats.Int64("number_of_mining_error", "Number of mining failures due to error", stats.UnitDimensionless)
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/zh/config-desc.md:
--------------------------------------------------------------------------------
1 | # 配置文件解析
2 |
3 | `sophon-miner` 的配置文件默认位于 `~/.sophon-miner/config.toml`,执行命令 `sophon-miner init` 时生成。文件中以 `#` 开头的行为注释。
4 |
5 | ## 新版本
6 |
7 | 版本号 `>= v1.7.0` 的版本.
8 |
9 | ```toml
10 | # 等待base延迟的秒数
11 | PropagationDelaySecs = 12
12 | # 计算出块证明等待超时
13 | MinerOnceTimeout = "15s"
14 | # 选择消息API的超时(单位:秒),0-不启用
15 | MpoolSelectDelaySecs = 0
16 |
17 | # `sophon-miner` 服务监听地址
18 | [API]
19 | ListenAddress = "/ip4/127.0.0.1/tcp/12308"
20 |
21 | [FullNode]
22 | Addr = "/ip4/127.0.0.1/tcp/3453"
23 | # [Gateway],[Auth],[FullNode] 是同一个token
24 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZWx2aW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.y2vEhxUd2Q9N6kYKe1nsKA4Xelam_ZIVRw7Ul_LgNkk"
25 |
26 | [Gateway]
27 | ListenAPI = ["/ip4/0.0.0.0/tcp/4569"]
28 | # [Gateway],[Auth],[FullNode] 是同一个token
29 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZWx2aW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.y2vEhxUd2Q9N6kYKe1nsKA4Xelam_ZIVRw7Ul_LgNkk"
30 |
31 | [Auth]
32 | Addr = "127.0.0.1:8989"
33 | # [Gateway],[Auth],[FullNode] 是同一个token
34 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZWx2aW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.y2vEhxUd2Q9N6kYKe1nsKA4Xelam_ZIVRw7Ul_LgNkk"
35 |
36 | [SlashFilter]
37 | Type = "local"
38 | [SlashFilter.MySQL]
39 | Conn = ""
40 | MaxOpenConn = 100
41 | MaxIdleConn = 10
42 | ConnMaxLifeTime = "1m0s"
43 | Debug = false
44 |
45 | [Tracing]
46 | JaegerTracingEnabled = false
47 | ProbabilitySampler = 1.0
48 | JaegerEndpoint = "localhost:6831"
49 | ServerName = ""
50 |
51 | [Metrics]
52 | # 是否开启metrics指标统计,默认为false
53 | Enabled = false
54 |
55 | [Metrics.Exporter]
56 | # 指标导出器类型,目前可选:prometheus或graphite,默认为prometheus
57 | Type = "prometheus"
58 |
59 | [Metrics.Exporter.Prometheus]
60 | # multiaddr
61 | EndPoint = "/ip4/0.0.0.0/tcp/12310"
62 | # 命名规范: "a_b_c", 不能带"-"
63 | Namespace = "miner"
64 | # 指标注册表类型,可选:default(默认,会附带程序运行的环境指标)或 define(自定义)
65 | RegistryType = "define"
66 | # prometheus 服务路径
67 | Path = "/debug/metrics"
68 | # 上报周期,单位为 秒(s)
69 | ReportingPeriod = 10
70 |
71 | [Metrics.Exporter.Graphite]
72 | # 命名规范: "a_b_c", 不能带"-"
73 | Namespace = "miner"
74 | # graphite exporter 收集器服务地址
75 | Host = "127.0.0.1"
76 | # graphite exporter 收集器服务监听端口
77 | Port = 12310
78 | # 上报周期,单位为 秒(s)
79 | ReportingPeriod = 10
80 | ```
81 |
82 | 如果选择 `Metrics.Exporter` 为 `Prometheus`, 可通过命令行快速查看指标:
83 |
84 | ```bash
85 | $ curl http://127.0.0.1:12310/debug/metrics
86 | # HELP miner_getbaseinfo_ms Duration of GetBaseInfo in miner
87 | # TYPE miner_getbaseinfo_ms histogram
88 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="100"} 50
89 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="200"} 51
90 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="400"} 51
91 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="600"} 51
92 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="800"} 51
93 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="1000"} 51
94 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="2000"} 51
95 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="20000"} 51
96 | miner_getbaseinfo_ms_bucket{miner_id="t010938",le="+Inf"} 51
97 | miner_getbaseinfo_ms_sum{miner_id="t010938"} 470.23516
98 | ... ...
99 | ```
100 | > 如果遇到错误 `curl: (56) Recv failure: Connection reset by peer`, 请使用本机 `ip` 地址, 如下所示:
101 | ```bash
102 | $ curl http://:12310/debug/metrics
103 | ```
104 |
105 | ### `SubmitNodes` 配置项解析
106 |
107 | 当选择消息的节点和广播区块的节点 `mpool` 中的消息不一致时, 有可能导致新出的区块无法验证,如区块中打包的消息在广播节点的 `mpool` 中不存在, 故引入`SubmitNodes`配置项, 保证区块正常上链.
108 |
109 | 默认的配置是空的,如下:
110 | ```toml
111 | SubmitNodes = []
112 | ```
113 |
114 | 当使用了 `chain-co` 组建时,需要将对接的所有同步节点配置到 `SubmitNodes`, 参考配置如下:
115 |
116 | ```toml
117 | [[SubmitNodes]]
118 | Addr = "/192.168.200.108/tcp/3453"
119 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2hhaW4tc2VydmljZSIsInBlcm0iOiJhZG1pbiIsImV4dCI6IiJ9.DxlsJO-XrrdQLvJdA6wdWJxeYOhZt_kMYMHc7NdfQNw"
120 |
121 | [[SubmitNodes]]
122 | Addr = "/ip4/192.168.200.107/tcp/3453"
123 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2hhaW4tc2VydmljZSIsInBlcm0iOiJhZG1pbiIsImV4dCI6IiJ9.DxlsJO-XrrdQLvJdA6wdWJxeYOhZt_kMYMHc7NdfQNw"
124 | ```
125 |
126 | ## 旧版本
127 |
128 | 旧版本指的是版本号 `< v1.7.0` 的版本
129 |
130 | ```toml
131 | # 链服务监听地址
132 | ListenAPI = "/ip4/127.0.0.1/tcp/3453"
133 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.RRSdeQ-c1Ei-8roAj-L-wpOr-y6PssDorbGijMPxjoc"
134 | # 生产的区块记录方式,已废弃,由 `slash filter` 取代
135 | BlockRecord = "cache"
136 |
137 | # `sophon-miner` 服务监听地址
138 | [API]
139 | ListenAddress = "/ip4/0.0.0.0/tcp/12308/http"
140 | RemoteListenAddress = ""
141 | Timeout = "30s"
142 |
143 | # 事件网关服务监听地址
144 | [Gateway]
145 | ListenAPI = ["/ip4/127.0.0.1/tcp/45132"]
146 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.RRSdeQ-c1Ei-8roAj-L-wpOr-y6PssDorbGijMPxjoc"
147 |
148 | # 数据库信息
149 | [Db]
150 | # 矿工管理方式,已废弃,从 `sophon-auth` 获取
151 | Type = "auth"
152 | # `slash filter` 模块区块存取方式
153 | SFType = "mysql"
154 | [Db.MySQL]
155 | Conn = "root:kuangfengjuexizhan@tcp(192.168.200.2:3308)/sophon-miner-butterfly-200-19?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s"
156 | MaxOpenConn = 100
157 | MaxIdleConn = 10
158 | ConnMaxLifeTime = 60
159 | Debug = false
160 | [Db.Auth]
161 | ListenAPI = "http://127.0.0.1:8989"
162 | Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJwZXJtIjoiYWRtaW4iLCJleHQiOiIifQ.RRSdeQ-c1Ei-8roAj-L-wpOr-y6PssDorbGijMPxjoc"
163 |
164 | # Jaeger Tracing 服务信息,默认不启用
165 | [Tracing]
166 | JaegerTracingEnabled = false
167 | JaegerEndpoint = "localhost:6831"
168 | ProbabilitySampler = 1.0
169 | ServerName = "sophon-miner"
170 | ```
171 |
172 |
--------------------------------------------------------------------------------
/docs/zh/design-spec.md:
--------------------------------------------------------------------------------
1 | # `sophon-miner`
2 |
3 | ## 简介
4 |
5 | `sophon-miner` 是 `Venus` 矿池中的链服务组件之一,是矿工出块的调度器。与 `PL` 实现的 `lotus-miner` 不同的是:
6 |
7 | - 支持多个矿工出块,`lotus-miner` 的一个实例负责单个矿工的出块;
8 |
9 | - 与 `Venus` 矿池中其他组件配合完成矿工的出块流程:
10 | - 调用 `venus-wallet` 进行签名及签名验证;
11 | - 调用 `venus-sealer`(将弃用) 或 `venus-cluster` 计算获胜证明。
12 |
13 | - 多个矿工合作出块,保证收益最大化。如:一个周期有多个出块时,打包不重复的消息,获得更多的小费,也可以保证消息及时上链,提升网络的TPS。
14 |
15 |
16 | ## 功能模块
17 |
18 | `sophon-miner` 的主要模块有:矿工管理,出块管理。
19 |
20 | ### 矿工管理
21 |
22 | `Venus` 矿池中的用户(或称为矿工)是由 `sophon-auth` 组件管理的,其记录了每个矿工的基础信息及身份验证信息。`sophon-miner` 从 `sophon-auth` 拉取最新的矿工列表,并周期性地进行区块生产流程。
23 |
24 | `sophon-miner` 可以暂停矿工列表中任意矿工的出块,比如某个矿工的签名节点失联时,可以手动暂停该矿工的出块流程,等待签名正常后再开启出块。
25 |
26 | `sophon-miner` 执行 `update` 更新矿工列表,通常在某些矿工退出矿池或有新的矿工加入矿池时进行。
27 |
28 | ### 出块管理
29 |
30 | `sophon-miner` 的一轮出块流程如下:
31 |
32 | - 请求同步节点获取 `Base`,即 `parent Tipset`(通常是最近一次有出块周期的 `Block`集)及空轮数(空轮表示该周期没有任何矿工出块);
33 |
34 | - 统计本周期获得出块权的矿工及出块必要数据,如随机数,选中扇区信息等;
35 |
36 | - 为每个获得出块权的矿工计算获胜证明,选择消息,创建区块;
37 |
38 | - 验证本周期生产的区块合法性(是否存在共识错误,因为广播具有共识错误的区块会受到一定的 `Fil` 惩罚),广播区块。
39 |
--------------------------------------------------------------------------------
/docs/zh/getting-start.md:
--------------------------------------------------------------------------------
1 | ## `sophon-miner` 快速启动
2 |
3 | ## 实例初始化
4 |
5 | ### 初始化 `Repo`
6 |
7 | **注意⚠️**
8 | 1. `init` 命令会连接 `--api`flag 指定的链节点,所以必须先启动链节点,才能初始化成功。
9 |
10 | `< v1.7.0` 版本:
11 | ```shell script
12 | $ ./sophon-miner init
13 | # For nettype, choose from mainnet, debug, 2k, calibnet
14 | --nettype=calibnet \
15 | --auth-api= \
16 | --token= \
17 | --gateway-api=/ip4//tcp/PORT \
18 | --api=/ip4//tcp/PORT \
19 | --slash-filter local
20 | ```
21 |
22 | `>= v1.7.0` 版本:
23 | ```shell script
24 | $ ./sophon-miner init
25 | --api=/ip4//tcp/PORT \
26 | --gateway-api=/ip4//tcp/PORT \
27 | --auth-api \
28 | --token \
29 | --slash-filter local
30 | ```
31 |
32 | ### 启动 `sophon-miner`
33 |
34 | ```shell script
35 | $ nohup ./sophon-miner run > miner.log 2>&1 &
36 | ```
37 |
38 | ## 矿工管理
39 |
40 | `sophon-miner` 启动时会从 `sophon-auth` 中拉取矿工列表,然后预执行一遍出块流程,如果失败可用 `state` 命令查看原因:
41 |
42 | ```shell script
43 | $ ./sophon-miner address state
44 | [
45 | {
46 | "Addr": "",
47 | "IsMining": true,
48 | "Err": null
49 | }
50 | ]
51 | ```
52 |
53 | 查看当前的矿工列表:
54 |
55 | ```shell script
56 | $ ./sophon-miner address list
57 | ```
58 |
59 |
60 | `start` 或 `stop` 命令可以开始或暂停某个矿工的的出块流程,如暂停 `f01008` 的出块流程:
61 |
62 | ```shell script
63 | $ ./sophon-miner address stop f01008
64 | ```
65 | > 注意:暂停后即使矿工有算力也不会执行出块,故在故障修复后需执行 `start` 开启出块流程。
66 |
67 |
68 | 有矿工加入或退出矿池,或矿工信息有变化时,需要重新从`venus_auth`拉取矿工列表:
69 |
70 | ```shell script
71 | $ ./sophon-miner address update
72 | ```
73 |
74 | ## 出块权统计
75 |
76 | 统计矿工在指定链高度区间内获得的出块权:
77 |
78 | ```shell script
79 | ./sophon-miner winner count --epoch-start= --epoch-end=
80 | ```
81 |
82 | > 这个是统计历史出块权的,不是预测未来的,所以: 1. `epoch-end` > `epoch-start`; 2. `epoch-end` 须小于当前链高度。
83 |
--------------------------------------------------------------------------------
/docs/zh/journal-event.md:
--------------------------------------------------------------------------------
1 | # Journal
2 |
3 | `Journal` 通过注册事件的方式,记录系统中特定的状态,将其输出到指定存储系统中,目前仅实现了文件系统。
4 |
5 | `Journal` 主要功能有两个:
6 |
7 | - `Journal Event`:注册事件,跟踪事件;
8 | - `Alerting`:依赖于`Journal Event` 的报警机制。
9 |
10 | 通过这两个功能,有助于辅助运维工作,跟踪系统的运行状况。
11 |
12 |
13 | ## Journal Event
14 |
15 | 通过事件方式记录需要的系统状态。可以是文件系统,也可以是其他存储系统,仅需实现接口:
16 |
17 | ```go
18 | type EventTypeRegistry interface {
19 |
20 | // RegisterEventType introduces a new event type to a journal, and
21 | // returns an EventType token that components can later use to check whether
22 | // journalling for that type is enabled/suppressed, and to tag journal
23 | // entries appropriately.
24 | RegisterEventType(system, event string) EventType
25 | }
26 |
27 | type Journal interface {
28 | EventTypeRegistry
29 |
30 | // RecordEvent records this event to the journal, if and only if the
31 | // EventType is enabled. If so, it calls the supplier function to obtain
32 | // the payload to record.
33 | //
34 | // Implementations MUST recover from panics raised by the supplier function.
35 | RecordEvent(evtType EventType, supplier func() interface{})
36 |
37 | // Close closes this journal for further writing.
38 | Close() error
39 | }
40 | ```
41 |
42 | 在 `sophon-miner` 系统中注册了以下事件: `System: "miner", Event: "block_mined"`,用于记录矿工的出块。在日志中以 `json` 方式保存:
43 | ```json
44 | {"System":"miner","Event":"block_mined","Timestamp":"2022-02-08T09:44:45.49744285+08:00","Data":{"cid":{"/":"bafy2bzacebur5zuktumidwj7hm6tv42f2cy6khwtk4zjz4434b5hfrdq2xcns"},"epoch":91552,"miner":"t01031","nulls":0,"parents":[{"/":"bafy2bzacect4kjtcml4uvgvmqtmbrdvh34zb73b5pqr7udjtzd5pnpa3lq5g4"}],"timestamp":1644284700}}
45 | ```
46 |
47 | ## Alerting
48 |
49 | `Alerting` 封装了 `Journal` 用于跟踪事件,通过 `AddAlertType`注册报警类型,调用`Raise` 及 `Resolve` 发起或解决报警。
50 |
51 | ```go
52 | type Alerting struct {
53 | j journal.Journal
54 |
55 | lk sync.Mutex
56 | alerts map[AlertType]Alert
57 | }
58 | ```
59 | `Alerting` 可用于监控系统资源,辅助运维做出合理的资源分配。
60 |
--------------------------------------------------------------------------------
/docs/zh/metrics.md:
--------------------------------------------------------------------------------
1 | ## `sophon-miner` 指标
2 |
3 | 有关开启指标,导出器的配置请参考[metrics config](./config-desc.md)
4 |
5 | ```
6 | // 类型:直方图,Tag:minerID,语义:获取 `BaseInfo` 耗时(单位:毫秒)
7 | GetBaseInfoDuration = stats.Float64("getbaseinfo_ms", "Duration of GetBaseInfo in miner", stats.UnitMilliseconds)
8 | // 类型:直方图,Tag:minerID,语义:计算 `ticket` 耗时(单位:毫秒)
9 | ComputeTicketDuration = stats.Float64("computeticket_ms", "Duration of ComputeTicket in miner", stats.UnitMilliseconds)
10 | // 类型:直方图,Tag:minerID,语义:计算是否赢得出块权耗时(单位:毫秒)
11 | IsRoundWinnerDuration = stats.Float64("isroundwinner_ms", "Duration of IsRoundWinner in miner", stats.UnitMilliseconds)
12 | // 类型:直方图,Tag:minerID,语义:计算获胜证明耗时(单位:秒)
13 | ComputeProofDuration = stats.Float64("computeproof_s", "Duration of ComputeProof in miner", stats.UnitSeconds)
14 |
15 | // 类型:计数器,Tag:minerID,语义:成功出块次数,指本地验证通过并成功广播,因base不足或共识错误没被承认没排除
16 | NumberOfBlock = stats.Int64("number_of_block", "Number of production blocks", stats.UnitDimensionless)
17 | // 类型:计数器,Tag:minerID,语义:获得出块权的次数,如果计算时处于分叉链上,则出块不会被承认
18 | NumberOfIsRoundWinner = stats.Int64("number_of_isroundwinner", "Number of is round winner", stats.UnitDimensionless)
19 |
20 | // 类型:计数器,Tag:minerID,语义:因计算获胜证明超时导致出块失败的次数
21 | NumberOfMiningTimeout = stats.Int64("number_of_mining_timeout", "Number of mining failures due to compute proof timeout", stats.UnitDimensionless)
22 | // 类型:计数器,Tag:minerID,语义:因链分叉放弃出块的次数,在分叉链上的出块没有意义,故在出块前验证到链分叉则放弃后续逻辑
23 | NumberOfMiningChainFork = stats.Int64("number_of_mining_chain_fork", "Number of mining failures due to chain fork", stats.UnitDimensionless)
24 | // 类型:计数器,Tag:minerID,语义:因其他错误导致出块错误的次数
25 | NumberOfMiningError = stats.Int64("number_of_mining_error", "Number of mining failures due to error", stats.UnitDimensionless)
26 | ```
27 |
28 |
29 | ### rpc
30 |
31 | ```
32 | # 调用无效RPC方法的次数
33 | RPCInvalidMethod = stats.Int64("rpc/invalid_method", "Total number of invalid RPC methods called", stats.UnitDimensionless)
34 | # RPC请求失败的次数
35 | RPCRequestError = stats.Int64("rpc/request_error", "Total number of request errors handled", stats.UnitDimensionless)
36 | # RPC响应失败的次数
37 | RPCResponseError = stats.Int64("rpc/response_error", "Total number of responses errors handled", stats.UnitDimensionless)
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/zh/mine-record.md:
--------------------------------------------------------------------------------
1 | ## 出块记录说明
2 |
3 | ### 出块记录是什么?
4 |
5 | 出块记录是 `venus-miner` 在计算出块时,保留在数据库中的,关于计算出块过程中出现的一些关键信息以及时间信息。出块记录一方面,可以用于链服务管理者排查出块过程中存在的问题,另一方面可以为链服务的用户,提供更加详细的出块信息,方便用户进行出块相关方面的分析。
6 |
7 | #### 出块记录的内容
8 | 一个典型的出块记录会包含一下字段:
9 | ```json
10 | {
11 | // 高度以及 miner 当下的基础信息
12 | "epoch": "814088",
13 | "miner": "t01038",
14 | "worker": "t3rjjw3z2rtxsplsbga4u...z4nylkbl4bbo4thh7ja",
15 | "minerPower": "1182013654564864",
16 | "networkPower": "5496119577509888",
17 |
18 | // 出块计算时间是否晚于理想时间节点
19 | "lateStart": "false",
20 |
21 | // 出块计算的父块信息
22 | "baseEpoch": "814087",
23 | "nullRounds": "0",
24 | "baseDelta": "21.004469998s",
25 | "beaconEpoch": "3221959",
26 |
27 | // 是否被允许参加出块选举
28 | "isEligible": "true",
29 |
30 | // 赢票,如果该字段没有出现,说明没有获得出块权,或者出块过程中出现了报错
31 | "winCount": "1",
32 |
33 | // 出块过程中产生的关键信息,例如:miner 没有获得出块权等
34 | "info":"",
35 |
36 | // 出块过程中的报错信息
37 | "error":"",
38 |
39 | // 出块过程中的起始时间戳,以及各阶段耗时
40 | "start": "2023-08-11 18:16:51.013248275 +0800 CST m=+67.262220230",
41 | "getBaseInfo": "8.442663ms",
42 | "computeElectionProof": "33.231212ms",
43 | "computeTicket": "33.637688ms",
44 | "seed": "5.689287ms",
45 | "computePostProof": "4.155131366s",
46 | "selectMessage": "3.640518079s",
47 | "createBlock": "35.231231ms",
48 | "end": "2023-08-11 18:16:58.925227964 +0800 CST m=+75.174199917",
49 | }
50 | ```
51 |
52 | 值得注意的是,上面各个字段都不是一定会出现的,例如:`error` 字段,如果出块过程中没有报错,那么这个字段就不会出现在出块记录中。如果没有获取到出块权的话,也不会有 `win_count` 字段。
53 |
54 | ### 使用
55 |
56 |
57 | 该功能启用后,`sophon-miner` 会记录七天的出块记录。可以使用 cli 查询,和请求接口查询等方式获取出块记录。
58 |
59 | #### 启用
60 |
61 | mine recorder 默认是关闭的,可以通过配置文件启用。
62 |
63 | ```toml
64 | [Recorder]
65 | # 是否启用
66 | # 默认 false
67 | Enable = true
68 |
69 | # 记录过期时间
70 | # 默认为 7天, 也就是 & * 2880
71 | ExpireEpoch = 0
72 |
73 | # 每次查询的最大记录数
74 | # 默认为 288
75 | MaxRecordPerQuery = 0
76 | ```
77 |
78 | #### 通过 cli 查询出块记录
79 |
80 | ```sh
81 | sophon-miner record query
82 | NAME:
83 | sophon-miner record query - query record
84 |
85 | USAGE:
86 | sophon-miner record query [command options]
87 |
88 | OPTIONS:
89 | --limit value query nums record of limit (default: 1)
90 |
91 | # 举例: 查询 t01038 在 814000 ~ 814199 的出块记录
92 | sophon-miner record query --limit 200 t01038 814000
93 | ```
94 |
95 | #### 通过接口查询出块记录
96 |
97 | Path: `/rpc/v0`
98 |
99 | Method: `POST`
100 |
101 | Header:
102 |
103 | `X-VENUS-API-NAMESPACE: miner.MinerAPI`,
104 |
105 | `Content-Type: application/json`,
106 |
107 | `Authorization: Bearer eyJhbGci...OiJIUzI`
108 |
109 | Body:
110 | ```json
111 | {
112 | "method": "Filecoin.QueryRecord",
113 | "params": [
114 | {
115 | "Miner": "t01038",
116 | "Epoch": 814000, //开始计算的高度
117 | "Limit": 200 //开始高度后的多少高度
118 | }
119 | ],
120 | "id": 0
121 | }
122 | ```
123 |
124 | Response:
125 | ```json
126 | [
127 | {
128 | "epoch": "814000",
129 | "miner": "t01038",
130 | "worker": "t3rjjw3z2rtxsplsbga4u...z4nylkbl4bbo4thh7ja",
131 | "minerPower": "1182013654564864",
132 | "networkPower": "5496119577509888",
133 | "lateStart": "false",
134 | "baseEpoch": "814087",
135 | "nullRounds": "0",
136 | "baseDelta": "21.004469998s",
137 | "beaconEpoch": "813999",
138 | "isEligible": "true",
139 | "winCount": "1",
140 | "info":"",
141 | "error":"",
142 | "start": "2023-08-11 18:16:51.013248275 +0800 CST m=+67.262220230",
143 | "getBaseInfo": "8.442663ms",
144 | "computeElectionProof": "33.231212ms",
145 | "computeTicket": "33.637688ms",
146 | "seed": "5.689287ms",
147 | "computePostProof": "4.155131366s",
148 | "selectMessage": "3.640518079s",
149 | "createBlock": "35.231231ms",
150 | "end": "2023-08-11 18:16:58.925227964 +0800 CST m=+75.174199917"
151 | },
152 | ,,,,,
153 | {
154 | "epoch": "814199",
155 | "miner": "t01038",
156 | "worker": "t3rjjw3z2rtxsplsbga4u...z4nylkbl4bbo4thh7ja",
157 | "minerPower": "1182013654564864",
158 | "networkPower": "5496119577509888",
159 | "lateStart": "false",
160 | "baseEpoch": "814198",
161 | "nullRounds": "0",
162 | "baseDelta": "21.004469998s",
163 | "beaconEpoch": "3221959",
164 | "isEligible": "true",
165 | "winCount": "1",
166 | "info":"",
167 | "error":"",
168 | "start": "2023-08-11 18:16:51.013248275 +0800 CST m=+67.262220230",
169 | "getBaseInfo": "8.442663ms",
170 | "computeElectionProof": "33.231212ms",
171 | "computeTicket": "33.637688ms",
172 | "seed": "5.689287ms",
173 | "computePostProof": "4.155131366s",
174 | "selectMessage": "3.640518079s",
175 | "createBlock": "35.231231ms",
176 | "end": "2023-08-11 18:16:58.925227964 +0800 CST m=+75.174199917"
177 | },
178 | ]
179 | ```
180 |
181 | ##### 使用示例
182 |
183 | ```sh
184 | # 使用 curl 命令查询
185 | curl http://gateway:45132/rpc/v0 -v -X POST -H "X-VENUS-API-NAMESPACE: miner.MinerAPI" -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs...HTQgbqUna4" -d '{"method": "Filecoin.QueryRecord", "params": [ { "Miner": "t01002" , "Epoch":27200 , "Limit":200 }
186 | ], "id": 0}'
187 | ```
188 |
--------------------------------------------------------------------------------
/f3participant/multiparticipation.go:
--------------------------------------------------------------------------------
1 | package f3participant
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/filecoin-project/go-address"
9 | v1api "github.com/filecoin-project/venus/venus-shared/api/chain/v1"
10 | "github.com/ipfs-force-community/sophon-miner/node/modules/helpers"
11 | miner_manager "github.com/ipfs-force-community/sophon-miner/node/modules/miner-manager"
12 | "github.com/jpillora/backoff"
13 | )
14 |
15 | const (
16 | // maxCheckProgressAttempts defines the maximum number of failed attempts
17 | // before we abandon the current lease and restart the participation process.
18 | //
19 | // The default backoff takes 12 attempts to reach a maximum delay of 1 minute.
20 | // Allowing for 13 failures results in approximately 2 minutes of backoff since
21 | // the lease was granted. Given a lease validity of up to 5 instances, this means
22 | // we would give up on checking the lease during its mid-validity period;
23 | // typically when we would try to renew the participation ticket. Hence, the value
24 | // to 13.
25 | checkProgressMaxAttempts = 13
26 |
27 | // F3LeaseTerm The number of instances the miner will attempt to lease from nodes.
28 | F3LeaseTerm = 5
29 | )
30 |
31 | type MultiParticipant struct {
32 | participants map[address.Address]*Participant
33 | minerManager miner_manager.MinerManageAPI
34 |
35 | newParticipant func(context.Context, address.Address) *Participant
36 | }
37 |
38 | func NewMultiParticipant(ctx helpers.MetricsCtx,
39 | node v1api.FullNode,
40 | minerManager miner_manager.MinerManageAPI,
41 | ) (*MultiParticipant, error) {
42 | newParticipant := func(ctx context.Context, participant address.Address) *Participant {
43 | return NewParticipant(
44 | ctx,
45 | node,
46 | participant,
47 | &backoff.Backoff{
48 | Min: 1 * time.Second,
49 | Max: 1 * time.Minute,
50 | Factor: 1.5,
51 | },
52 | checkProgressMaxAttempts,
53 | F3LeaseTerm,
54 | )
55 | }
56 |
57 | miners, err := minerManager.List(ctx)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | mp := &MultiParticipant{
63 | participants: make(map[address.Address]*Participant),
64 | minerManager: minerManager,
65 | newParticipant: newParticipant,
66 | }
67 |
68 | for _, minerInfo := range miners {
69 | if !minerManager.IsOpenMining(ctx, minerInfo.Addr) {
70 | continue
71 | }
72 | p := newParticipant(ctx, minerInfo.Addr)
73 | mp.participants[minerInfo.Addr] = p
74 | }
75 |
76 | return mp, nil
77 | }
78 |
79 | func (mp *MultiParticipant) Start(ctx context.Context) error {
80 | for _, p := range mp.participants {
81 | if err := p.Start(ctx); err != nil {
82 | return err
83 | }
84 | }
85 | go mp.MonitorMiner(ctx)
86 |
87 | return nil
88 | }
89 |
90 | func (mp *MultiParticipant) Stop(ctx context.Context) error {
91 | for _, p := range mp.participants {
92 | if err := p.Stop(ctx); err != nil {
93 | return fmt.Errorf("failed to stop participant %v, err: %v", p, err)
94 | }
95 | }
96 | return nil
97 | }
98 |
99 | func (mp *MultiParticipant) addParticipant(ctx helpers.MetricsCtx, participant address.Address) error {
100 | p, ok := mp.participants[participant]
101 | if ok {
102 | return nil
103 | }
104 | mp.participants[participant] = mp.newParticipant(ctx, participant)
105 | log.Infof("add participate %s", participant)
106 |
107 | return p.Start(ctx)
108 | }
109 |
110 | func (mp *MultiParticipant) removeParticipant(ctx context.Context, participant address.Address) error {
111 | p, ok := mp.participants[participant]
112 | if !ok {
113 | return nil
114 | }
115 | delete(mp.participants, participant)
116 | log.Infof("remove participate %s", participant)
117 |
118 | return p.Stop(ctx)
119 | }
120 |
121 | func (mp *MultiParticipant) MonitorMiner(ctx context.Context) {
122 | ticker := time.NewTicker(10 * time.Minute)
123 | defer ticker.Stop()
124 |
125 | for {
126 | select {
127 | case <-ctx.Done():
128 | return
129 | case <-ticker.C:
130 | miners, err := mp.minerManager.List(ctx)
131 | if err != nil {
132 | log.Errorf("failed to list miners: %v", err)
133 | continue
134 | }
135 |
136 | for _, minerInfo := range miners {
137 | if !mp.minerManager.IsOpenMining(ctx, minerInfo.Addr) {
138 | if err := mp.removeParticipant(ctx, minerInfo.Addr); err != nil {
139 | log.Errorf("failed to remove participate %s: %v", minerInfo.Addr, err)
140 | }
141 | continue
142 | }
143 |
144 | if _, ok := mp.participants[minerInfo.Addr]; !ok {
145 | if err := mp.addParticipant(ctx, minerInfo.Addr); err != nil {
146 | log.Errorf("failed to add participate %s: %v", minerInfo.Addr, err)
147 | }
148 | }
149 | }
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/lib/journal/alerting/alerts.go:
--------------------------------------------------------------------------------
1 | package alerting
2 |
3 | import (
4 | "encoding/json"
5 | "sort"
6 | "sync"
7 | "time"
8 |
9 | "github.com/ipfs-force-community/sophon-miner/lib/journal"
10 | logging "github.com/ipfs/go-log/v2"
11 | )
12 |
13 | var log = logging.Logger("alerting")
14 |
15 | // Alerting provides simple stateful alert system. Consumers can register alerts,
16 | // which can be raised and resolved.
17 | //
18 | // When an alert is raised or resolved, a related journal entry is recorded.
19 | type Alerting struct {
20 | j journal.Journal
21 |
22 | lk sync.Mutex
23 | alerts map[AlertType]Alert
24 | }
25 |
26 | // AlertType is a unique alert identifier
27 | type AlertType struct {
28 | System, Subsystem string
29 | }
30 |
31 | // AlertEvent contains information about alert state transition
32 | type AlertEvent struct {
33 | Type string // either 'raised' or 'resolved'
34 | Message json.RawMessage
35 | Time time.Time
36 | }
37 |
38 | type Alert struct {
39 | Type AlertType
40 | Active bool
41 |
42 | LastActive *AlertEvent // NOTE: pointer for nullability, don't mutate the referenced object!
43 | LastResolved *AlertEvent
44 |
45 | journalType journal.EventType
46 | }
47 |
48 | func NewAlertingSystem(j journal.Journal) *Alerting {
49 | return &Alerting{
50 | j: j,
51 |
52 | alerts: map[AlertType]Alert{},
53 | }
54 | }
55 |
56 | func (a *Alerting) AddAlertType(system, subsystem string) AlertType {
57 | a.lk.Lock()
58 | defer a.lk.Unlock()
59 |
60 | at := AlertType{
61 | System: system,
62 | Subsystem: subsystem,
63 | }
64 |
65 | if _, exists := a.alerts[at]; exists {
66 | return at
67 | }
68 |
69 | et := a.j.RegisterEventType(system, subsystem)
70 |
71 | a.alerts[at] = Alert{
72 | Type: at,
73 | Active: false,
74 | journalType: et,
75 | }
76 |
77 | return at
78 | }
79 |
80 | func (a *Alerting) update(at AlertType, message interface{}, upd func(Alert, json.RawMessage) Alert) {
81 | a.lk.Lock()
82 | defer a.lk.Unlock()
83 |
84 | alert, ok := a.alerts[at]
85 | if !ok {
86 | log.Errorw("unknown alert", "type", at, "message", message)
87 | }
88 |
89 | rawMsg, err := json.Marshal(message)
90 | if err != nil {
91 | log.Errorw("marshaling alert message failed", "type", at, "error", err)
92 | rawMsg, err = json.Marshal(&struct {
93 | AlertError string
94 | }{
95 | AlertError: err.Error(),
96 | })
97 | log.Errorw("marshaling marshaling error failed", "type", at, "error", err)
98 | }
99 |
100 | a.alerts[at] = upd(alert, rawMsg)
101 | }
102 |
103 | // Raise marks the alert condition as active and records related event in the journal
104 | func (a *Alerting) Raise(at AlertType, message interface{}) {
105 | log.Errorw("alert raised", "type", at, "message", message)
106 |
107 | a.update(at, message, func(alert Alert, rawMsg json.RawMessage) Alert {
108 | alert.Active = true
109 | alert.LastActive = &AlertEvent{
110 | Type: "raised",
111 | Message: rawMsg,
112 | Time: time.Now(),
113 | }
114 |
115 | a.j.RecordEvent(alert.journalType, func() interface{} {
116 | return alert.LastActive
117 | })
118 |
119 | return alert
120 | })
121 | }
122 |
123 | // Resolve marks the alert condition as resolved and records related event in the journal
124 | func (a *Alerting) Resolve(at AlertType, message interface{}) {
125 | log.Errorw("alert resolved", "type", at, "message", message)
126 |
127 | a.update(at, message, func(alert Alert, rawMsg json.RawMessage) Alert {
128 | alert.Active = false
129 | alert.LastResolved = &AlertEvent{
130 | Type: "resolved",
131 | Message: rawMsg,
132 | Time: time.Now(),
133 | }
134 |
135 | a.j.RecordEvent(alert.journalType, func() interface{} {
136 | return alert.LastResolved
137 | })
138 |
139 | return alert
140 | })
141 | }
142 |
143 | // GetAlerts returns all registered (active and inactive) alerts
144 | func (a *Alerting) GetAlerts() []Alert {
145 | a.lk.Lock()
146 | defer a.lk.Unlock()
147 |
148 | out := make([]Alert, 0, len(a.alerts))
149 | for _, alert := range a.alerts {
150 | out = append(out, alert)
151 | }
152 | sort.Slice(out, func(i, j int) bool {
153 | if out[i].Type.System != out[j].Type.System {
154 | return out[i].Type.System < out[j].Type.System
155 | }
156 |
157 | return out[i].Type.Subsystem < out[j].Type.Subsystem
158 | })
159 |
160 | return out
161 | }
162 |
--------------------------------------------------------------------------------
/lib/journal/alerting/alerts_test.go:
--------------------------------------------------------------------------------
1 | // stm: #unit
2 | package alerting
3 |
4 | import (
5 | "encoding/json"
6 | "testing"
7 |
8 | mock "github.com/golang/mock/gomock"
9 | "github.com/stretchr/testify/require"
10 |
11 | "github.com/ipfs-force-community/sophon-miner/lib/journal"
12 | "github.com/ipfs-force-community/sophon-miner/lib/journal/mockjournal"
13 | )
14 |
15 | func TestAlerting(t *testing.T) {
16 | mockCtrl := mock.NewController(t)
17 | defer mockCtrl.Finish()
18 | j := mockjournal.NewMockJournal(mockCtrl)
19 |
20 | a := NewAlertingSystem(j)
21 |
22 | j.EXPECT().RegisterEventType("s1", "b1").Return(journal.EventType{System: "s1", Event: "b1"})
23 | // stm: @VENUSMINER_ALERTING_ADD_ALERT_TYPE_001
24 | al1 := a.AddAlertType("s1", "b1")
25 |
26 | j.EXPECT().RegisterEventType("s2", "b2").Return(journal.EventType{System: "s2", Event: "b2"})
27 | // stm: @VENUSMINER_ALERTING_ADD_ALERT_TYPE_001
28 | al2 := a.AddAlertType("s2", "b2")
29 |
30 | l := a.GetAlerts()
31 | require.Len(t, l, 2)
32 | require.Equal(t, al1, l[0].Type)
33 | require.Equal(t, al2, l[1].Type)
34 |
35 | for _, alert := range l {
36 | require.False(t, alert.Active)
37 | require.Nil(t, alert.LastActive)
38 | require.Nil(t, alert.LastResolved)
39 | }
40 |
41 | j.EXPECT().RecordEvent(a.alerts[al1].journalType, mock.Any())
42 | a.Raise(al1, "test")
43 |
44 | for _, alert := range l { // check for no magic mutations
45 | require.False(t, alert.Active)
46 | require.Nil(t, alert.LastActive)
47 | require.Nil(t, alert.LastResolved)
48 | }
49 |
50 | // stm: @VENUSMINER_ALERTING_GET_ALERTS_001
51 | l = a.GetAlerts()
52 | require.Len(t, l, 2)
53 | require.Equal(t, al1, l[0].Type)
54 | require.Equal(t, al2, l[1].Type)
55 |
56 | require.True(t, l[0].Active)
57 | require.NotNil(t, l[0].LastActive)
58 | require.Equal(t, "raised", l[0].LastActive.Type)
59 | require.Equal(t, json.RawMessage(`"test"`), l[0].LastActive.Message)
60 | require.Nil(t, l[0].LastResolved)
61 |
62 | require.False(t, l[1].Active)
63 | require.Nil(t, l[1].LastActive)
64 | require.Nil(t, l[1].LastResolved)
65 |
66 | var lastResolved *AlertEvent
67 |
68 | resolveMesage := "test resolve"
69 | j.EXPECT().RecordEvent(a.alerts[al1].journalType, mock.Any()).Do(
70 | func(arg0 interface{}, cb func() interface{}) {
71 | lastResolved = cb().(*AlertEvent)
72 | })
73 | // stm: @VENUSMINER_ALERTING_RAISE_001
74 | a.Resolve(al1, resolveMesage)
75 | l = a.GetAlerts()
76 |
77 | var resolvedAlert *Alert
78 | for _, alert := range l {
79 | if alert.Type.System == "s1" && alert.Type.Subsystem == "b1" {
80 | resolvedAlert = &alert
81 | break
82 | }
83 | }
84 | require.NotNil(t, resolvedAlert)
85 | require.NotNil(t, resolvedAlert.LastResolved)
86 | require.False(t, resolvedAlert.Active)
87 | require.Equal(t, resolvedAlert.LastResolved, lastResolved)
88 | }
89 |
--------------------------------------------------------------------------------
/lib/journal/env.go:
--------------------------------------------------------------------------------
1 | package journal
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | // envJournalDisabledEvents is the environment variable through which disabled
8 | // journal events can be customized.
9 | const envDisabledEvents = "SOPHON_MINER_JOURNAL_DISABLED_EVENTS"
10 |
11 | func EnvDisabledEvents() DisabledEvents {
12 | if env, ok := os.LookupEnv(envDisabledEvents); ok {
13 | if ret, err := ParseDisabledEvents(env); err == nil {
14 | return ret
15 | }
16 | }
17 | // fallback if env variable is not set, or if it failed to parse.
18 | return DefaultDisabledEvents
19 | }
20 |
--------------------------------------------------------------------------------
/lib/journal/fsjournal/fs.go:
--------------------------------------------------------------------------------
1 | package fsjournal
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 |
9 | logging "github.com/ipfs/go-log/v2"
10 |
11 | "github.com/ipfs-force-community/sophon-miner/build"
12 | "github.com/ipfs-force-community/sophon-miner/lib/journal"
13 | "github.com/ipfs-force-community/sophon-miner/node/repo"
14 | )
15 |
16 | var log = logging.Logger("fsjournal")
17 |
18 | const RFC3339nocolon = "2006-01-02T150405Z0700"
19 |
20 | // fsJournal is a basic journal backed by files on a filesystem.
21 | type fsJournal struct {
22 | journal.EventTypeRegistry
23 |
24 | dir string
25 | sizeLimit int64
26 |
27 | fi *os.File
28 | fSize int64
29 |
30 | incoming chan *journal.Event
31 |
32 | closing chan struct{}
33 | closed chan struct{}
34 | }
35 |
36 | // OpenFSJournal constructs a rolling filesystem journal, with a default
37 | // per-file size limit of 1GiB.
38 | func OpenFSJournal(lr repo.LockedRepo, disabled journal.DisabledEvents) (journal.Journal, error) {
39 | dir := filepath.Join(lr.Path(), "journal")
40 | if err := os.MkdirAll(dir, 0755); err != nil {
41 | return nil, fmt.Errorf("failed to mk directory %s for file journal: %w", dir, err)
42 | }
43 |
44 | f := &fsJournal{
45 | EventTypeRegistry: journal.NewEventTypeRegistry(disabled),
46 | dir: dir,
47 | sizeLimit: 1 << 30,
48 | incoming: make(chan *journal.Event, 32),
49 | closing: make(chan struct{}),
50 | closed: make(chan struct{}),
51 | }
52 |
53 | if err := f.rollJournalFile(); err != nil {
54 | return nil, err
55 | }
56 |
57 | go f.runLoop()
58 |
59 | return f, nil
60 | }
61 |
62 | func (f *fsJournal) RecordEvent(evtType journal.EventType, supplier func() interface{}) {
63 | defer func() {
64 | if r := recover(); r != nil {
65 | log.Warnf("recovered from panic while recording journal event; type=%s, err=%v", evtType, r)
66 | }
67 | }()
68 |
69 | if !evtType.Enabled() {
70 | return
71 | }
72 |
73 | je := &journal.Event{
74 | EventType: evtType,
75 | Timestamp: build.Clock.Now(),
76 | Data: supplier(),
77 | }
78 | select {
79 | case f.incoming <- je:
80 | case <-f.closing:
81 | log.Warnw("journal closed but tried to log event", "event", je)
82 | }
83 | }
84 |
85 | func (f *fsJournal) Close() error {
86 | close(f.closing)
87 | <-f.closed
88 | return nil
89 | }
90 |
91 | func (f *fsJournal) putEvent(evt *journal.Event) error {
92 | b, err := json.Marshal(evt)
93 | if err != nil {
94 | return err
95 | }
96 | n, err := f.fi.Write(append(b, '\n'))
97 | if err != nil {
98 | return err
99 | }
100 |
101 | f.fSize += int64(n)
102 |
103 | if f.fSize >= f.sizeLimit {
104 | _ = f.rollJournalFile()
105 | }
106 |
107 | return nil
108 | }
109 |
110 | func (f *fsJournal) rollJournalFile() error {
111 | if f.fi != nil {
112 | _ = f.fi.Close()
113 | }
114 | current := filepath.Join(f.dir, "sophon-miner-journal.ndjson")
115 | rolled := filepath.Join(f.dir, fmt.Sprintf(
116 | "sophon-miner-journal-%s.ndjson",
117 | build.Clock.Now().Format(RFC3339nocolon),
118 | ))
119 |
120 | // check if journal file exists
121 | if fi, err := os.Stat(current); err == nil && !fi.IsDir() {
122 | err := os.Rename(current, rolled)
123 | if err != nil {
124 | return fmt.Errorf("failed to roll journal file: %w", err)
125 | }
126 | }
127 |
128 | nfi, err := os.Create(current)
129 | if err != nil {
130 | return fmt.Errorf("failed to create journal file: %w", err)
131 | }
132 |
133 | f.fi = nfi
134 | f.fSize = 0
135 |
136 | return nil
137 | }
138 |
139 | func (f *fsJournal) runLoop() {
140 | defer close(f.closed)
141 |
142 | for {
143 | select {
144 | case je := <-f.incoming:
145 | if err := f.putEvent(je); err != nil {
146 | log.Errorw("failed to write out journal event", "event", je, "err", err)
147 | }
148 | case <-f.closing:
149 | _ = f.fi.Close()
150 | return
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/lib/journal/fsjournal/fs_test.go:
--------------------------------------------------------------------------------
1 | // stm: #unit
2 | package fsjournal
3 |
4 | import (
5 | "os"
6 | "path/filepath"
7 | "testing"
8 |
9 | "github.com/ipfs-force-community/sophon-miner/lib/journal"
10 | "github.com/ipfs-force-community/sophon-miner/node/repo"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | func TestFsJournal(t *testing.T) {
15 | tmp := t.TempDir()
16 | repo, err := repo.NewFS(tmp)
17 | require.NoError(t, err)
18 | lr, err := repo.Lock()
19 | require.NoError(t, err)
20 |
21 | dir := filepath.Join(lr.Path(), "journal")
22 | require.NoError(t, os.WriteFile(dir, []byte("file exists\n"), 0644))
23 |
24 | // stm: @VENUSMINER_JOURNAL_ENV_DISABLED_EVENTS_001
25 | envDisableEvent := journal.EnvDisabledEvents()
26 | require.Zero(t, len(envDisableEvent))
27 |
28 | {
29 | // 'tmpdir/journal' is a file would cause an error
30 | // stm: @VENUSMINER_FSJOURNAL_OPEN_FS_JOURNAL_002
31 | _, err = OpenFSJournal(lr, envDisableEvent)
32 | require.Error(t, err)
33 | require.NoError(t, os.RemoveAll(dir))
34 | }
35 |
36 | jlFile := filepath.Join(dir, "sophon-miner-journal.ndjson")
37 | {
38 | // If there is an error on rollJournalFile return nil and error.
39 | // stm: @VENUSMINER_FSJOURNAL_OPEN_FS_JOURNAL_003
40 | require.NoError(t, os.MkdirAll(jlFile, 0755))
41 |
42 | _, err := OpenFSJournal(lr, envDisableEvent)
43 | require.Error(t, err)
44 | require.NoError(t, os.RemoveAll(dir))
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/journal/mockjournal/journal.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: github.com/ipfs-force-community/sophon-miner/lib/journal (interfaces: Journal)
3 |
4 | // Package mockjournal is a generated GoMock package.
5 | package mockjournal
6 |
7 | import (
8 | reflect "reflect"
9 |
10 | gomock "github.com/golang/mock/gomock"
11 | journal "github.com/ipfs-force-community/sophon-miner/lib/journal"
12 | )
13 |
14 | // MockJournal is a mock of Journal interface.
15 | type MockJournal struct {
16 | ctrl *gomock.Controller
17 | recorder *MockJournalMockRecorder
18 | }
19 |
20 | // MockJournalMockRecorder is the mock recorder for MockJournal.
21 | type MockJournalMockRecorder struct {
22 | mock *MockJournal
23 | }
24 |
25 | // NewMockJournal creates a new mock instance.
26 | func NewMockJournal(ctrl *gomock.Controller) *MockJournal {
27 | mock := &MockJournal{ctrl: ctrl}
28 | mock.recorder = &MockJournalMockRecorder{mock}
29 | return mock
30 | }
31 |
32 | // EXPECT returns an object that allows the caller to indicate expected use.
33 | func (m *MockJournal) EXPECT() *MockJournalMockRecorder {
34 | return m.recorder
35 | }
36 |
37 | // Close mocks base method.
38 | func (m *MockJournal) Close() error {
39 | m.ctrl.T.Helper()
40 | ret := m.ctrl.Call(m, "Close")
41 | ret0, _ := ret[0].(error)
42 | return ret0
43 | }
44 |
45 | // Close indicates an expected call of Close.
46 | func (mr *MockJournalMockRecorder) Close() *gomock.Call {
47 | mr.mock.ctrl.T.Helper()
48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockJournal)(nil).Close))
49 | }
50 |
51 | // RecordEvent mocks base method.
52 | func (m *MockJournal) RecordEvent(arg0 journal.EventType, arg1 func() interface{}) {
53 | m.ctrl.T.Helper()
54 | m.ctrl.Call(m, "RecordEvent", arg0, arg1)
55 | }
56 |
57 | // RecordEvent indicates an expected call of RecordEvent.
58 | func (mr *MockJournalMockRecorder) RecordEvent(arg0, arg1 interface{}) *gomock.Call {
59 | mr.mock.ctrl.T.Helper()
60 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecordEvent", reflect.TypeOf((*MockJournal)(nil).RecordEvent), arg0, arg1)
61 | }
62 |
63 | // RegisterEventType mocks base method.
64 | func (m *MockJournal) RegisterEventType(arg0, arg1 string) journal.EventType {
65 | m.ctrl.T.Helper()
66 | ret := m.ctrl.Call(m, "RegisterEventType", arg0, arg1)
67 | ret0, _ := ret[0].(journal.EventType)
68 | return ret0
69 | }
70 |
71 | // RegisterEventType indicates an expected call of RegisterEventType.
72 | func (mr *MockJournalMockRecorder) RegisterEventType(arg0, arg1 interface{}) *gomock.Call {
73 | mr.mock.ctrl.T.Helper()
74 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterEventType", reflect.TypeOf((*MockJournal)(nil).RegisterEventType), arg0, arg1)
75 | }
76 |
--------------------------------------------------------------------------------
/lib/journal/nil.go:
--------------------------------------------------------------------------------
1 | package journal
2 |
3 | type nilJournal struct{}
4 |
5 | // nilj is a singleton nil journal.
6 | var nilj Journal = &nilJournal{}
7 |
8 | func NilJournal() Journal {
9 | return nilj
10 | }
11 |
12 | func (n *nilJournal) RegisterEventType(_, _ string) EventType { return EventType{} }
13 |
14 | func (n *nilJournal) RecordEvent(_ EventType, _ func() interface{}) {}
15 |
16 | func (n *nilJournal) Close() error { return nil }
17 |
--------------------------------------------------------------------------------
/lib/journal/registry.go:
--------------------------------------------------------------------------------
1 | package journal
2 |
3 | import "sync"
4 |
5 | // EventTypeRegistry is a component that constructs tracked EventType tokens,
6 | // for usage with a Journal.
7 | type EventTypeRegistry interface {
8 |
9 | // RegisterEventType introduces a new event type to a journal, and
10 | // returns an EventType token that components can later use to check whether
11 | // journalling for that type is enabled/suppressed, and to tag journal
12 | // entries appropriately.
13 | RegisterEventType(system, event string) EventType
14 | }
15 |
16 | // eventTypeRegistry is an embeddable mixin that takes care of tracking disabled
17 | // event types, and returning initialized/safe EventTypes when requested.
18 | type eventTypeRegistry struct {
19 | sync.Mutex
20 |
21 | m map[string]EventType
22 | }
23 |
24 | var _ EventTypeRegistry = (*eventTypeRegistry)(nil)
25 |
26 | func NewEventTypeRegistry(disabled DisabledEvents) EventTypeRegistry {
27 | ret := &eventTypeRegistry{
28 | m: make(map[string]EventType, len(disabled)+32), // + extra capacity.
29 | }
30 |
31 | for _, et := range disabled {
32 | et.enabled, et.safe = false, true
33 | ret.m[et.System+":"+et.Event] = et
34 | }
35 |
36 | return ret
37 | }
38 |
39 | func (d *eventTypeRegistry) RegisterEventType(system, event string) EventType {
40 | d.Lock()
41 | defer d.Unlock()
42 |
43 | key := system + ":" + event
44 | if et, ok := d.m[key]; ok {
45 | return et
46 | }
47 |
48 | et := EventType{
49 | System: system,
50 | Event: event,
51 | enabled: true,
52 | safe: true,
53 | }
54 |
55 | d.m[key] = et
56 | return et
57 | }
58 |
--------------------------------------------------------------------------------
/lib/journal/registry_test.go:
--------------------------------------------------------------------------------
1 | // stm: #unit
2 | package journal
3 |
4 | import (
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestDisabledEvents(t *testing.T) {
11 | req := require.New(t)
12 |
13 | // stm: @VENUSMINER_JOURNAL_NEW_EVENT_TYPE_REGISTRY_001, @VENUSMINER_JOURNAL_REGISTER_EVENT_TYPE_001, @VENUSMINER_JOURNAL_ENABLED_001
14 | test := func(dis DisabledEvents) func(*testing.T) {
15 | return func(t *testing.T) {
16 | registry := NewEventTypeRegistry(dis)
17 |
18 | reg1 := registry.RegisterEventType("system1", "disabled1")
19 | reg2 := registry.RegisterEventType("system1", "disabled2")
20 |
21 | req.False(reg1.Enabled())
22 | req.False(reg2.Enabled())
23 | req.True(reg1.safe)
24 | req.True(reg2.safe)
25 |
26 | reg3 := registry.RegisterEventType("system3", "enabled3")
27 | req.True(reg3.Enabled())
28 | req.True(reg3.safe)
29 | }
30 | }
31 |
32 | t.Run("direct", test(DisabledEvents{
33 | EventType{System: "system1", Event: "disabled1"},
34 | EventType{System: "system1", Event: "disabled2"},
35 | }))
36 |
37 | // stm: @VENUSMINER_JOURNAL_PARSE_DISABLED_EVENTS_001
38 | dis, err := ParseDisabledEvents("system1:disabled1,system1:disabled2")
39 | req.NoError(err)
40 |
41 | t.Run("parsed", test(dis))
42 |
43 | dis, err = ParseDisabledEvents(" system1:disabled1 , system1:disabled2 ")
44 | req.NoError(err)
45 |
46 | t.Run("parsed_spaces", test(dis))
47 | }
48 |
49 | func TestParseDisableEvents(t *testing.T) {
50 | // stm: @VENUSMINER_JOURNAL_PARSE_DISABLED_EVENTS_001
51 | _, err := ParseDisabledEvents("system1:disabled1:failed,system1:disabled2")
52 | require.Error(t, err)
53 | }
54 |
--------------------------------------------------------------------------------
/lib/journal/types.go:
--------------------------------------------------------------------------------
1 | package journal
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 | )
8 |
9 | var (
10 | // DefaultDisabledEvents lists the journal events disabled by
11 | // default, usually because they are considered noisy.
12 | DefaultDisabledEvents = DisabledEvents{}
13 | )
14 |
15 | // DisabledEvents is the set of event types whose journaling is suppressed.
16 | type DisabledEvents []EventType
17 |
18 | // ParseDisabledEvents parses a string of the form: "system1:event1,system1:event2[,...]"
19 | // into a DisabledEvents object, returning an error if the string failed to parse.
20 | //
21 | // It sanitizes strings via strings.TrimSpace.
22 | func ParseDisabledEvents(s string) (DisabledEvents, error) {
23 | s = strings.TrimSpace(s) // sanitize
24 | evts := strings.Split(s, ",")
25 | ret := make(DisabledEvents, 0, len(evts))
26 | for _, evt := range evts {
27 | evt = strings.TrimSpace(evt) // sanitize
28 | s := strings.Split(evt, ":")
29 | if len(s) != 2 {
30 | return nil, fmt.Errorf("invalid event type: %s", s)
31 | }
32 | ret = append(ret, EventType{System: s[0], Event: s[1]})
33 | }
34 | return ret, nil
35 | }
36 |
37 | // EventType represents the signature of an event.
38 | type EventType struct {
39 | System string
40 | Event string
41 |
42 | // enabled stores whether this event type is enabled.
43 | enabled bool
44 |
45 | // safe is a sentinel marker that's set to true if this EventType was
46 | // constructed correctly (via Journal#RegisterEventType).
47 | safe bool
48 | }
49 |
50 | func (et EventType) String() string {
51 | return et.System + ":" + et.Event
52 | }
53 |
54 | // Enabled returns whether this event type is enabled in the journaling
55 | // subsystem. Users are advised to check this before actually attempting to
56 | // add a journal entry, as it helps bypass object construction for events that
57 | // would be discarded anyway.
58 | //
59 | // All event types are enabled by default, and specific event types can only
60 | // be disabled at Journal construction time.
61 | func (et EventType) Enabled() bool {
62 | return et.safe && et.enabled
63 | }
64 |
65 | func NewEventType(sys, evt string, enable, safe bool) EventType {
66 | return EventType{System: sys, Event: evt, enabled: true, safe: true}
67 | }
68 |
69 | //go:generate go run github.com/golang/mock/mockgen -destination=mockjournal/journal.go -package=mockjournal . Journal
70 |
71 | // Journal represents an audit trail of system actions.
72 | //
73 | // Every entry is tagged with a timestamp, a system name, and an event name.
74 | // The supplied data can be any type, as long as it is JSON serializable,
75 | // including structs, map[string]interface{}, or primitive types.
76 | //
77 | // For cleanliness and type safety, we recommend to use typed events. See the
78 | // *Evt struct types in this package for more info.
79 | type Journal interface {
80 | EventTypeRegistry
81 |
82 | // RecordEvent records this event to the journal, if and only if the
83 | // EventType is enabled. If so, it calls the supplier function to obtain
84 | // the payload to record.
85 | //
86 | // Implementations MUST recover from panics raised by the supplier function.
87 | RecordEvent(evtType EventType, supplier func() interface{})
88 |
89 | // Close closes this journal for further writing.
90 | Close() error
91 | }
92 |
93 | // Event represents a journal entry.
94 | //
95 | // See godocs on Journal for more information.
96 | type Event struct {
97 | EventType
98 |
99 | Timestamp time.Time
100 | Data interface{}
101 | }
102 |
--------------------------------------------------------------------------------
/lib/logger/levels.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "os"
5 |
6 | logging "github.com/ipfs/go-log/v2"
7 | )
8 |
9 | func SetupLogLevels() {
10 | if _, set := os.LookupEnv("GOLOG_LOG_LEVEL"); !set {
11 | _ = logging.SetLogLevel("*", "INFO")
12 | _ = logging.SetLogLevel("miner", "DEBUG")
13 | }
14 | // Always mute RtRefreshManager because it breaks terminals
15 | _ = logging.SetLogLevel("dht/RtRefreshManager", "FATAL")
16 | }
17 |
--------------------------------------------------------------------------------
/lib/metrics/expoter.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "go.opencensus.io/stats/view"
8 |
9 | "github.com/ipfs-force-community/metrics"
10 | )
11 |
12 | func SetupMetrics(ctx context.Context, metricsConfig *metrics.MetricsConfig) error {
13 | if err := view.Register(
14 | MinerNodeViews...,
15 | ); err != nil {
16 | return fmt.Errorf("cannot register the view: %w", err)
17 | }
18 |
19 | return metrics.SetupMetrics(ctx, metricsConfig)
20 | }
21 |
--------------------------------------------------------------------------------
/lib/metrics/metrics.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | rpcMetrics "github.com/filecoin-project/go-jsonrpc/metrics"
8 | "github.com/ipfs-force-community/metrics"
9 | "go.opencensus.io/stats"
10 | "go.opencensus.io/stats/view"
11 | "go.opencensus.io/tag"
12 | )
13 |
14 | // Global Tags
15 | var (
16 | MinerID, _ = tag.NewKey("miner_id")
17 | )
18 |
19 | var (
20 | NumberOfBlock = stats.Int64("number_of_block", "Number of production blocks", stats.UnitDimensionless)
21 | NumberOfIsRoundWinner = stats.Int64("number_of_isroundwinner", "Number of is round winner", stats.UnitDimensionless)
22 |
23 | NumberOfMiningTimeout = stats.Int64("number_of_mining_timeout", "Number of mining failures due to compute proof timeout", stats.UnitDimensionless)
24 | NumberOfMiningChainFork = stats.Int64("number_of_mining_chain_fork", "Number of mining failures due to chain fork", stats.UnitDimensionless)
25 | NumberOfMiningError = stats.Int64("number_of_mining_error", "Number of mining failures due to error", stats.UnitDimensionless)
26 | )
27 |
28 | var (
29 | NumberOfBlockView = &view.View{
30 | Measure: NumberOfBlock,
31 | Aggregation: view.Count(),
32 | TagKeys: []tag.Key{MinerID},
33 | }
34 | IsRoundWinnerView = &view.View{
35 | Measure: NumberOfIsRoundWinner,
36 | Aggregation: view.Count(),
37 | TagKeys: []tag.Key{MinerID},
38 | }
39 | NumberOfMiningTimeoutView = &view.View{
40 | Measure: NumberOfMiningTimeout,
41 | Aggregation: view.Count(),
42 | TagKeys: []tag.Key{MinerID},
43 | }
44 | NumberOfMiningChainForkView = &view.View{
45 | Measure: NumberOfMiningChainFork,
46 | Aggregation: view.Count(),
47 | TagKeys: []tag.Key{MinerID},
48 | }
49 | NumberOfMiningErrorView = &view.View{
50 | Measure: NumberOfMiningError,
51 | Aggregation: view.Count(),
52 | TagKeys: []tag.Key{MinerID},
53 | }
54 | )
55 |
56 | var (
57 | MinerNumInState = metrics.NewInt64WithCategory("miner/num", "miner num in vary state", "")
58 |
59 | GetBaseInfoDuration = metrics.NewTimerMs("mine/getbaseinfo", "Duration of GetBaseInfo in miner", MinerID)
60 | ComputeTicketDuration = metrics.NewTimerMs("mine/computeticket", "Duration of ComputeTicket in miner", MinerID)
61 | CheckRoundWinnerDuration = metrics.NewTimerMs("mine/checkroundwinner", "Duration of Check Round Winner in miner", MinerID)
62 | ComputeProofDuration = metrics.NewTimerMs("mine/computeproof", "Duration of ComputeProof in miner", MinerID)
63 | )
64 |
65 | var MinerNodeViews = append([]*view.View{
66 | NumberOfBlockView,
67 | IsRoundWinnerView,
68 | NumberOfMiningTimeoutView,
69 | NumberOfMiningChainForkView,
70 | NumberOfMiningErrorView,
71 | }, rpcMetrics.DefaultViews...)
72 |
73 | func SinceInMilliseconds(startTime time.Time) float64 {
74 | return float64(time.Since(startTime).Nanoseconds()) / 1e6
75 | }
76 |
77 | func TimerMilliseconds(ctx context.Context, m *stats.Float64Measure, minerID string) func() {
78 | start := time.Now()
79 | return func() {
80 | ctx, _ = tag.New(
81 | ctx,
82 | tag.Upsert(MinerID, minerID),
83 | )
84 | stats.Record(ctx, m.M(SinceInMilliseconds(start)))
85 | }
86 | }
87 |
88 | func SinceInSeconds(startTime time.Time) float64 {
89 | return float64(time.Since(startTime).Microseconds()) / 1e6
90 | }
91 |
92 | func TimerSeconds(ctx context.Context, m *stats.Float64Measure, minerID string) func() {
93 | start := time.Now()
94 | return func() {
95 | ctx, _ = tag.New(
96 | ctx,
97 | tag.Upsert(MinerID, minerID),
98 | )
99 | stats.Record(ctx, m.M(SinceInSeconds(start)))
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/tracing/setup.go:
--------------------------------------------------------------------------------
1 | package tracing
2 |
3 | import (
4 | "os"
5 |
6 | "contrib.go.opencensus.io/exporter/jaeger"
7 | logging "github.com/ipfs/go-log/v2"
8 | "go.opencensus.io/trace"
9 |
10 | "github.com/ipfs-force-community/metrics"
11 | )
12 |
13 | var log = logging.Logger("tracing")
14 |
15 | func SetupJaegerTracing(cfg *metrics.TraceConfig) *jaeger.Exporter {
16 | if !cfg.JaegerTracingEnabled {
17 | return nil
18 | }
19 |
20 | agentEndpointURI := cfg.JaegerEndpoint
21 | if _, ok := os.LookupEnv("SOPHON_MINER_JAEGER"); ok {
22 | agentEndpointURI = os.Getenv("SOPHON_MINER_JAEGER")
23 | }
24 |
25 | je, err := jaeger.NewExporter(jaeger.Options{
26 | AgentEndpoint: agentEndpointURI,
27 | Process: jaeger.Process{
28 | ServiceName: cfg.ServerName,
29 | },
30 | })
31 | if err != nil {
32 | log.Errorw("Failed to create the Jaeger exporter", "error", err)
33 | return nil
34 | }
35 |
36 | trace.RegisterExporter(je)
37 | trace.ApplyConfig(trace.Config{
38 | DefaultSampler: trace.ProbabilitySampler(cfg.ProbabilitySampler),
39 | })
40 |
41 | log.Infof("register tracing exporter:%s, service name:%s", cfg.JaegerEndpoint, cfg.ServerName)
42 |
43 | return je
44 | }
45 |
--------------------------------------------------------------------------------
/miner/api.go:
--------------------------------------------------------------------------------
1 | package miner
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/filecoin-project/go-address"
7 | "github.com/filecoin-project/go-state-types/abi"
8 |
9 | "github.com/ipfs-force-community/sophon-miner/types"
10 | )
11 |
12 | type MiningAPI interface {
13 | IMinerMining
14 | IMinerManager
15 | }
16 |
17 | type IMinerMining interface {
18 | Start(context.Context) error
19 | Stop(context.Context) error
20 | ManualStart(context.Context, []address.Address) error
21 | ManualStop(context.Context, []address.Address) error
22 | }
23 |
24 | type IMinerManager interface {
25 | UpdateAddress(context.Context, int64, int64) ([]types.MinerInfo, error)
26 | ListAddress(context.Context) ([]types.MinerInfo, error)
27 | StatesForMining(context.Context, []address.Address) ([]types.MinerState, error)
28 | CountWinners(context.Context, []address.Address, abi.ChainEpoch, abi.ChainEpoch) ([]types.CountWinners, error)
29 | ListBlocks(ctx context.Context, params *types.BlocksQueryParams) ([]types.MinedBlock, error)
30 | WarmupForMiner(context.Context, address.Address) error
31 | QueryRecord(ctx context.Context, params *types.QueryRecordParams) ([]map[string]string, error)
32 | }
33 |
--------------------------------------------------------------------------------
/miner/minerwpp.go:
--------------------------------------------------------------------------------
1 | package miner
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/filecoin-project/go-address"
9 | "github.com/filecoin-project/go-state-types/abi"
10 | "github.com/filecoin-project/go-state-types/network"
11 |
12 | "github.com/ipfs-force-community/sophon-miner/api/client"
13 | "github.com/ipfs-force-community/sophon-miner/build"
14 | "github.com/ipfs-force-community/sophon-miner/node/config"
15 |
16 | "github.com/filecoin-project/venus/pkg/constants"
17 | "github.com/filecoin-project/venus/venus-shared/actors/builtin"
18 | v1 "github.com/filecoin-project/venus/venus-shared/api/chain/v1"
19 | "github.com/filecoin-project/venus/venus-shared/types"
20 | )
21 |
22 | type MiningWpp struct {
23 | mAddr address.Address
24 | gatewayNode *config.GatewayNode
25 |
26 | winnRpt abi.RegisteredPoStProof
27 | }
28 |
29 | func NewWinningPoStProver(api v1.FullNode, gatewayNode *config.GatewayNode, mAddr address.Address) (*MiningWpp, error) {
30 | mi, err := api.StateMinerInfo(context.TODO(), mAddr, types.EmptyTSK)
31 | if err != nil {
32 | return nil, fmt.Errorf("getting sector size: %w", err)
33 | }
34 |
35 | if constants.InsecurePoStValidation {
36 | log.Warn("*****************************************************************************")
37 | log.Warn(" Generating fake PoSt proof! You should only see this while running tests! ")
38 | log.Warn("*****************************************************************************")
39 | }
40 |
41 | return &MiningWpp{gatewayNode: gatewayNode, mAddr: mAddr, winnRpt: mi.WindowPoStProofType}, nil
42 | }
43 |
44 | var _ WinningPoStProver = (*MiningWpp)(nil)
45 |
46 | func (wpp *MiningWpp) GenerateCandidates(ctx context.Context, randomness abi.PoStRandomness, eligibleSectorCount uint64) ([]uint64, error) {
47 | panic("should not be called")
48 | }
49 |
50 | func (wpp *MiningWpp) ComputeProof(ctx context.Context, ssi []builtin.ExtendedSectorInfo, rand abi.PoStRandomness, currEpoch abi.ChainEpoch, nv network.Version) ([]builtin.PoStProof, error) {
51 | if constants.InsecurePoStValidation {
52 | return []builtin.PoStProof{{ProofBytes: []byte("valid proof")}}, nil
53 | }
54 |
55 | log.Infof("Computing WinningPoSt ;%+v; %v", ssi, rand)
56 |
57 | start := build.Clock.Now()
58 |
59 | api, closer, err := client.NewGatewayRPC(ctx, wpp.gatewayNode)
60 | if err != nil {
61 | return nil, err
62 | }
63 | defer closer()
64 |
65 | proofBuf, err := api.ComputeProof(ctx, wpp.mAddr, ssi, rand, currEpoch, nv)
66 | if err != nil {
67 | return nil, err
68 | }
69 |
70 | log.Infof("GenerateWinningPoSt took %s", time.Since(start))
71 | return proofBuf, nil
72 | }
73 |
--------------------------------------------------------------------------------
/miner/mock/mock_post_provider.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: ./miner/util.go
3 |
4 | // Package mock is a generated GoMock package.
5 | package mock
6 |
7 | import (
8 | context "context"
9 | reflect "reflect"
10 |
11 | abi "github.com/filecoin-project/go-state-types/abi"
12 | network "github.com/filecoin-project/go-state-types/network"
13 | builtin "github.com/filecoin-project/venus/venus-shared/actors/builtin"
14 | gomock "github.com/golang/mock/gomock"
15 | )
16 |
17 | // MockWinningPoStProver is a mock of WinningPoStProver interface.
18 | type MockWinningPoStProver struct {
19 | ctrl *gomock.Controller
20 | recorder *MockWinningPoStProverMockRecorder
21 | }
22 |
23 | // MockWinningPoStProverMockRecorder is the mock recorder for MockWinningPoStProver.
24 | type MockWinningPoStProverMockRecorder struct {
25 | mock *MockWinningPoStProver
26 | }
27 |
28 | // NewMockWinningPoStProver creates a new mock instance.
29 | func NewMockWinningPoStProver(ctrl *gomock.Controller) *MockWinningPoStProver {
30 | mock := &MockWinningPoStProver{ctrl: ctrl}
31 | mock.recorder = &MockWinningPoStProverMockRecorder{mock}
32 | return mock
33 | }
34 |
35 | // EXPECT returns an object that allows the caller to indicate expected use.
36 | func (m *MockWinningPoStProver) EXPECT() *MockWinningPoStProverMockRecorder {
37 | return m.recorder
38 | }
39 |
40 | // ComputeProof mocks base method.
41 | func (m *MockWinningPoStProver) ComputeProof(arg0 context.Context, arg1 []builtin.ExtendedSectorInfo, arg2 abi.PoStRandomness, arg3 abi.ChainEpoch, arg4 network.Version) ([]builtin.PoStProof, error) {
42 | m.ctrl.T.Helper()
43 | ret := m.ctrl.Call(m, "ComputeProof", arg0, arg1, arg2, arg3, arg4)
44 | ret0, _ := ret[0].([]builtin.PoStProof)
45 | ret1, _ := ret[1].(error)
46 | return ret0, ret1
47 | }
48 |
49 | // ComputeProof indicates an expected call of ComputeProof.
50 | func (mr *MockWinningPoStProverMockRecorder) ComputeProof(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
51 | mr.mock.ctrl.T.Helper()
52 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ComputeProof", reflect.TypeOf((*MockWinningPoStProver)(nil).ComputeProof), arg0, arg1, arg2, arg3, arg4)
53 | }
54 |
55 | // GenerateCandidates mocks base method.
56 | func (m *MockWinningPoStProver) GenerateCandidates(arg0 context.Context, arg1 abi.PoStRandomness, arg2 uint64) ([]uint64, error) {
57 | m.ctrl.T.Helper()
58 | ret := m.ctrl.Call(m, "GenerateCandidates", arg0, arg1, arg2)
59 | ret0, _ := ret[0].([]uint64)
60 | ret1, _ := ret[1].(error)
61 | return ret0, ret1
62 | }
63 |
64 | // GenerateCandidates indicates an expected call of GenerateCandidates.
65 | func (mr *MockWinningPoStProverMockRecorder) GenerateCandidates(arg0, arg1, arg2 interface{}) *gomock.Call {
66 | mr.mock.ctrl.T.Helper()
67 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateCandidates", reflect.TypeOf((*MockWinningPoStProver)(nil).GenerateCandidates), arg0, arg1, arg2)
68 | }
69 |
--------------------------------------------------------------------------------
/miner/util.go:
--------------------------------------------------------------------------------
1 | package miner
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/binary"
7 | "fmt"
8 |
9 | "github.com/minio/blake2b-simd"
10 |
11 | "github.com/filecoin-project/go-address"
12 | "github.com/filecoin-project/go-state-types/abi"
13 | "github.com/filecoin-project/go-state-types/crypto"
14 | "github.com/filecoin-project/go-state-types/network"
15 |
16 | "github.com/filecoin-project/venus/venus-shared/actors/builtin"
17 | "github.com/filecoin-project/venus/venus-shared/types"
18 | "github.com/filecoin-project/venus/venus-shared/types/wallet"
19 | )
20 |
21 | type WinningPoStProver interface {
22 | GenerateCandidates(context.Context, abi.PoStRandomness, uint64) ([]uint64, error)
23 | ComputeProof(context.Context, []builtin.ExtendedSectorInfo, abi.PoStRandomness, abi.ChainEpoch, network.Version) ([]builtin.PoStProof, error)
24 | }
25 |
26 | type SignFunc func(ctx context.Context, signer address.Address, accounts []string, toSign []byte, meta types.MsgMeta) (*crypto.Signature, error)
27 |
28 | func ComputeVRF(ctx context.Context, sign SignFunc, account string, worker address.Address, sigInput []byte) ([]byte, error) {
29 | sig, err := sign(ctx, worker, []string{account}, sigInput, types.MsgMeta{Type: types.MTDrawRandomParam})
30 | if err != nil {
31 | return nil, err
32 | }
33 |
34 | if sig.Type != crypto.SigTypeBLS {
35 | return nil, fmt.Errorf("miner worker address was not a BLS key")
36 | }
37 |
38 | return sig.Data, nil
39 | }
40 |
41 | func IsRoundWinner(
42 | ctx context.Context,
43 | round abi.ChainEpoch,
44 | account string,
45 | miner address.Address,
46 | brand types.BeaconEntry,
47 | mbi *types.MiningBaseInfo,
48 | sign SignFunc,
49 | ) (*types.ElectionProof, error) {
50 |
51 | buf := new(bytes.Buffer)
52 | if err := miner.MarshalCBOR(buf); err != nil {
53 | return nil, fmt.Errorf("failed to cbor marshal address: %w", err)
54 | }
55 |
56 | electionRand := new(bytes.Buffer)
57 | drp := &wallet.DrawRandomParams{
58 | Rbase: brand.Data,
59 | Pers: crypto.DomainSeparationTag_ElectionProofProduction,
60 | Round: round,
61 | Entropy: buf.Bytes(),
62 | }
63 | err := drp.MarshalCBOR(electionRand)
64 | if err != nil {
65 | return nil, fmt.Errorf("failed to marshal randomness: %w", err)
66 | }
67 | //electionRand, err := chain.DrawRandomness(brand.Data, crypto.DomainSeparationTag_ElectionProofProduction, round, buf.Bytes())
68 | //if err != nil {
69 | // return nil, fmt.Errorf("failed to draw randomness: %w", err)
70 | //}
71 |
72 | vrfout, err := ComputeVRF(ctx, sign, account, mbi.WorkerKey, electionRand.Bytes())
73 | if err != nil {
74 | return nil, fmt.Errorf("failed to compute VRF: %w", err)
75 | }
76 |
77 | ep := &types.ElectionProof{VRFProof: vrfout}
78 | j := ep.ComputeWinCount(mbi.MinerPower, mbi.NetworkPower)
79 | ep.WinCount = j
80 | if j < 1 {
81 | return nil, nil
82 | }
83 |
84 | return ep, nil
85 | }
86 |
87 | // DrawRandomness todo 在venus处理好后,这里的删除,用venus中的函数
88 | func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) {
89 | h := blake2b.New256()
90 | if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil {
91 | return nil, fmt.Errorf("deriving randomness: %s", err)
92 | }
93 | VRFDigest := blake2b.Sum256(rbase)
94 | _, err := h.Write(VRFDigest[:])
95 | if err != nil {
96 | return nil, fmt.Errorf("hashing VRFDigest: %s", err)
97 | }
98 | if err := binary.Write(h, binary.BigEndian, round); err != nil {
99 | return nil, fmt.Errorf("deriving randomness: %s", err)
100 | }
101 | _, err = h.Write(entropy)
102 | if err != nil {
103 | return nil, fmt.Errorf("hashing entropy: %s", err)
104 | }
105 |
106 | return h.Sum(nil), nil
107 | }
108 |
109 | // ReorgOps takes two tipsets (which can be at different heights), and walks
110 | // their corresponding chains backwards one step at a time until we find
111 | // a common ancestor. It then returns the respective chain segments that fork
112 | // from the identified ancestor, in reverse order, where the first element of
113 | // each slice is the supplied tipset, and the last element is the common
114 | // ancestor.
115 | //
116 | // If an error happens along the way, we return the error with nil slices.
117 | // todo should move this code into store.ReorgOps. anywhere use this function should invoke store.ReorgOps
118 | // todo 因依赖filecoin-ffi,暂时从venus复制的,venus处理好依赖后删除,用venus中的
119 | func ReorgOps(lts func(context.Context, types.TipSetKey) (*types.TipSet, error), a, b *types.TipSet) ([]*types.TipSet, []*types.TipSet, error) {
120 | left := a
121 | right := b
122 |
123 | var leftChain, rightChain []*types.TipSet
124 | for !left.Equals(right) {
125 | if left.Height() > right.Height() {
126 | leftChain = append(leftChain, left)
127 | par, err := lts(context.TODO(), left.Parents())
128 | if err != nil {
129 | return nil, nil, err
130 | }
131 |
132 | left = par
133 | } else {
134 | rightChain = append(rightChain, right)
135 | par, err := lts(context.TODO(), right.Parents())
136 | if err != nil {
137 | log.Infof("failed to fetch right.Parents: %s", err)
138 | return nil, nil, err
139 | }
140 |
141 | right = par
142 | }
143 | }
144 |
145 | return leftChain, rightChain, nil
146 | }
147 |
--------------------------------------------------------------------------------
/miner/warmup.go:
--------------------------------------------------------------------------------
1 | package miner
2 |
3 | import (
4 | "context"
5 | "crypto/rand"
6 | "fmt"
7 | "math"
8 | "time"
9 |
10 | "github.com/filecoin-project/go-address"
11 | "github.com/filecoin-project/go-bitfield"
12 | "github.com/filecoin-project/go-state-types/abi"
13 | proof7 "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof"
14 |
15 | "github.com/filecoin-project/venus/venus-shared/types"
16 | )
17 |
18 | func (m *Miner) winPoStWarmup(ctx context.Context) error {
19 | m.lkWPP.Lock()
20 | defer m.lkWPP.Unlock()
21 | for addr, wpp := range m.minerWPPMap {
22 | tAddr := addr
23 | epp := wpp.epp
24 | go func() {
25 | err := m.winPostWarmupForMiner(ctx, tAddr, epp)
26 | if err != nil {
27 | log.Warnw("mining warm up failed", "miner", tAddr, "err", err.Error())
28 | m.minerWPPMap[tAddr].err = append(m.minerWPPMap[tAddr].err, time.Now().Format("2006-01-02 15:04:05 ")+err.Error())
29 | }
30 | }()
31 | }
32 |
33 | return nil
34 | }
35 |
36 | func (m *Miner) WarmupForMiner(ctx context.Context, mAddr address.Address) error {
37 | m.lkWPP.Lock()
38 | wpp, ok := m.minerWPPMap[mAddr]
39 | m.lkWPP.Unlock()
40 |
41 | if ok {
42 | return m.winPostWarmupForMiner(ctx, mAddr, wpp.epp)
43 | }
44 |
45 | return fmt.Errorf("%s not exist", mAddr)
46 | }
47 |
48 | func (m *Miner) winPostWarmupForMiner(ctx context.Context, addr address.Address, epp WinningPoStProver) error {
49 | deadlines, err := m.api.StateMinerDeadlines(ctx, addr, types.EmptyTSK)
50 | if err != nil {
51 | return fmt.Errorf("getting deadlines: %w", err)
52 | }
53 |
54 | var sector abi.SectorNumber = math.MaxUint64
55 |
56 | out:
57 | for dlIdx := range deadlines {
58 | partitions, err := m.api.StateMinerPartitions(ctx, addr, uint64(dlIdx), types.EmptyTSK)
59 | if err != nil {
60 | return fmt.Errorf("getting partitions for deadline %d: %w", dlIdx, err)
61 | }
62 |
63 | for _, partition := range partitions {
64 | b, err := partition.ActiveSectors.First()
65 | if err == bitfield.ErrNoBitsSet {
66 | continue
67 | }
68 | if err != nil {
69 | return err
70 | }
71 |
72 | sector = abi.SectorNumber(b)
73 | break out
74 | }
75 | }
76 |
77 | if sector == math.MaxUint64 {
78 | log.Info("skipping winning PoSt warmup, no sectors")
79 | return nil
80 | }
81 |
82 | log.Infow("starting winning PoSt warmup", "sector", sector)
83 | start := time.Now()
84 |
85 | var r abi.PoStRandomness = make([]byte, abi.RandomnessLength)
86 | _, _ = rand.Read(r)
87 |
88 | si, err := m.api.StateSectorGetInfo(ctx, addr, sector, types.EmptyTSK)
89 | if err != nil {
90 | return fmt.Errorf("getting sector info: %w", err)
91 | }
92 |
93 | if si == nil {
94 | return fmt.Errorf("sector not found %d", sector)
95 | }
96 |
97 | ts, err := m.api.ChainHead(ctx)
98 | if err != nil {
99 | return fmt.Errorf("getting chain head")
100 | }
101 | nv, err := m.api.StateNetworkVersion(ctx, ts.Key())
102 | if err != nil {
103 | return fmt.Errorf("getting network version")
104 | }
105 |
106 | _, err = epp.ComputeProof(ctx, []proof7.ExtendedSectorInfo{
107 | {
108 | SealProof: si.SealProof,
109 | SectorNumber: sector,
110 | SealedCID: si.SealedCID,
111 | SectorKey: si.SectorKeyCID,
112 | },
113 | }, r, ts.Height(), nv)
114 | if err != nil {
115 | return fmt.Errorf("failed to compute proof: %w", err)
116 | }
117 |
118 | log.Infow("winning PoSt warmup successful", "took", time.Since(start))
119 | return nil
120 | }
121 |
122 | func (m *Miner) doWinPoStWarmup(ctx context.Context) {
123 | err := m.winPoStWarmup(ctx)
124 | if err != nil {
125 | log.Errorw("winning PoSt warmup failed", "error", err)
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/node/config/apiinfo.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 |
7 | "github.com/multiformats/go-multiaddr"
8 | manet "github.com/multiformats/go-multiaddr/net"
9 | )
10 |
11 | type APIInfo struct {
12 | Addr string
13 | Token string
14 | }
15 |
16 | func defaultAPIInfo() *APIInfo {
17 | return &APIInfo{
18 | Addr: "/ip4/0.0.0.0/tcp/12308/http",
19 | Token: "",
20 | }
21 | }
22 |
23 | func (a APIInfo) DialArgs(version string) (string, error) {
24 | ma, err := multiaddr.NewMultiaddr(a.Addr)
25 | if err == nil {
26 | _, addr, err := manet.DialArgs(ma)
27 | if err != nil {
28 | return "", err
29 | }
30 |
31 | return "ws://" + addr + "/rpc/" + version, nil
32 | }
33 |
34 | _, err = url.Parse(a.Addr)
35 | if err != nil {
36 | return "", err
37 | }
38 | return a.Addr + "/rpc/" + version, nil
39 | }
40 |
41 | func (a APIInfo) Host() (string, error) {
42 | ma, err := multiaddr.NewMultiaddr(a.Addr)
43 | if err == nil {
44 | _, addr, err := manet.DialArgs(ma)
45 | if err != nil {
46 | return "", err
47 | }
48 |
49 | return addr, nil
50 | }
51 |
52 | spec, err := url.Parse(a.Addr)
53 | if err != nil {
54 | return "", err
55 | }
56 | return spec.Host, nil
57 | }
58 |
59 | func (a APIInfo) AuthHeader() http.Header {
60 | if len(a.Token) != 0 {
61 | headers := http.Header{}
62 | headers.Add("Authorization", "Bearer "+string(a.Token))
63 | return headers
64 | }
65 | log.Warn("Sealer API Token not set and requested, capabilities might be limited.")
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/node/config/apiinfo_test.go:
--------------------------------------------------------------------------------
1 | // stm: #unit
2 | package config
3 |
4 | import (
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestAPIInfo(t *testing.T) {
11 | apiInfo := defaultAPIInfo()
12 | // stm: @VENUSMINER_NODECONFIGAPIINFO_DIAL_ARGS_001
13 | url, err := apiInfo.DialArgs("0")
14 | assert.NoError(t, err)
15 | assert.Greater(t, len(url), 0)
16 |
17 | // stm: @VENUSMINER_NODECONFIGAPIINFO_HOST_001
18 | host, err := apiInfo.Host()
19 | assert.NoError(t, err)
20 | assert.Equal(t, host, "0.0.0.0:12308")
21 |
22 | // stm: @VENUSMINER_NODECONFIGAPIINFO_AUTH_HEADER_001
23 | apiInfo.Token = "hello"
24 | header := apiInfo.AuthHeader()
25 | assert.Equal(t, header.Get("Authorization"), "Bearer "+apiInfo.Token)
26 | }
27 |
--------------------------------------------------------------------------------
/node/config/def.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "net/url"
6 | "time"
7 |
8 | logging "github.com/ipfs/go-log/v2"
9 |
10 | "github.com/ipfs-force-community/metrics"
11 | )
12 |
13 | var log = logging.Logger("config")
14 |
15 | // Duration is a wrapper type for Duration
16 | // for decoding and encoding from/to TOML
17 | type Duration time.Duration
18 |
19 | // UnmarshalText implements interface for TOML decoding
20 | func (dur *Duration) UnmarshalText(text []byte) error {
21 | d, err := time.ParseDuration(string(text))
22 | if err != nil {
23 | return err
24 | }
25 | *dur = Duration(d)
26 | return err
27 | }
28 |
29 | func (dur Duration) MarshalText() ([]byte, error) {
30 | d := time.Duration(dur)
31 | return []byte(d.String()), nil
32 | }
33 |
34 | type MySQLConfig struct {
35 | Conn string
36 | MaxOpenConn int // 100
37 | MaxIdleConn int // 10
38 | ConnMaxLifeTime Duration // 60s
39 | Debug bool
40 | }
41 |
42 | type SlashFilterConfig struct {
43 | Type string
44 | MySQL MySQLConfig
45 | }
46 |
47 | type RecorderConfig struct {
48 | Enable bool
49 | ExpireEpoch uint64
50 | MaxRecordPerQuery uint
51 | }
52 |
53 | func newSlashFilterConfig() *SlashFilterConfig {
54 | return &SlashFilterConfig{
55 | Type: "local",
56 | MySQL: MySQLConfig{
57 | Conn: "",
58 | MaxOpenConn: 100,
59 | MaxIdleConn: 10,
60 | ConnMaxLifeTime: Duration(60 * time.Second),
61 | Debug: false,
62 | },
63 | }
64 | }
65 |
66 | type API struct {
67 | ListenAddress string
68 | }
69 |
70 | type MinerConfig struct {
71 | API API
72 | FullNode *APIInfo
73 | Gateway *GatewayNode
74 | Auth *APIInfo
75 | SubmitNodes []*APIInfo
76 | F3Node *APIInfo
77 |
78 | PropagationDelaySecs uint64
79 | MpoolSelectDelaySecs uint64
80 | MinerOnceTimeout Duration
81 |
82 | SlashFilter *SlashFilterConfig
83 | Recorder *RecorderConfig
84 |
85 | Tracing *metrics.TraceConfig
86 | Metrics *metrics.MetricsConfig
87 | }
88 |
89 | func DefaultMinerConfig() *MinerConfig {
90 | minerCfg := &MinerConfig{
91 | API: API{
92 | ListenAddress: "/ip4/127.0.0.1/tcp/12308",
93 | },
94 | FullNode: defaultAPIInfo(),
95 | Gateway: newDefaultGatewayNode(),
96 | Auth: defaultAPIInfo(),
97 | PropagationDelaySecs: 12,
98 | MinerOnceTimeout: Duration(time.Second * 15),
99 | MpoolSelectDelaySecs: 0,
100 | SlashFilter: newSlashFilterConfig(),
101 | Tracing: metrics.DefaultTraceConfig(),
102 | Metrics: metrics.DefaultMetricsConfig(),
103 | }
104 |
105 | return minerCfg
106 | }
107 |
108 | func Check(cfg *MinerConfig) error {
109 | if len(cfg.API.ListenAddress) == 0 {
110 | return fmt.Errorf("must config listen address")
111 | }
112 |
113 | if len(cfg.FullNode.Addr) == 0 || len(cfg.FullNode.Token) == 0 {
114 | return fmt.Errorf("must config full node url and token")
115 | }
116 |
117 | _, err := url.Parse(cfg.Auth.Addr)
118 | if err != nil {
119 | return fmt.Errorf("auth url format not correct %s %w", cfg.Auth.Addr, err)
120 | }
121 |
122 | if len(cfg.Gateway.ListenAPI) == 0 {
123 | return fmt.Errorf("config at lease one gateway url")
124 | }
125 |
126 | if cfg.SlashFilter.Type == "mysql" {
127 | if len(cfg.SlashFilter.MySQL.Conn) == 0 {
128 | return fmt.Errorf("mysql dsn must set when slash filter is mysql")
129 | }
130 | } else if cfg.SlashFilter.Type == "local" {
131 | } else {
132 | return fmt.Errorf("not support slash filter %s", cfg.SlashFilter.Type)
133 | }
134 | return nil
135 | }
136 |
--------------------------------------------------------------------------------
/node/config/def_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "bytes"
5 | "reflect"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/BurntSushi/toml"
10 | "github.com/stretchr/testify/require"
11 | )
12 |
13 | func TestDefaultMinerRoundtrip(t *testing.T) {
14 | c := DefaultMinerConfig()
15 |
16 | var s string
17 | {
18 | buf := new(bytes.Buffer)
19 | _, _ = buf.WriteString("# Default config:\n")
20 | e := toml.NewEncoder(buf)
21 | require.NoError(t, e.Encode(c))
22 |
23 | s = buf.String()
24 | }
25 |
26 | c2, err := FromReader(strings.NewReader(s), DefaultMinerConfig())
27 | require.NoError(t, err)
28 |
29 | require.True(t, reflect.DeepEqual(c, c2))
30 | }
31 |
--------------------------------------------------------------------------------
/node/config/gatewaydef.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/filecoin-project/venus/venus-shared/api"
7 | )
8 |
9 | type GatewayNode struct {
10 | ListenAPI []string
11 | Token string
12 | }
13 |
14 | func (gw *GatewayNode) DialArgs() ([]string, error) {
15 | var mAddrs []string
16 |
17 | for _, apiAddr := range gw.ListenAPI {
18 | apiInfo := api.NewAPIInfo(apiAddr, gw.Token)
19 | addr, err := apiInfo.DialArgs("v2")
20 | if err != nil {
21 | log.Errorf("dial ma err: %s", err.Error())
22 | continue
23 | }
24 |
25 | mAddrs = append(mAddrs, addr)
26 | }
27 |
28 | return mAddrs, nil
29 | }
30 |
31 | func (gw *GatewayNode) AuthHeader() http.Header {
32 | if len(gw.Token) != 0 {
33 | headers := http.Header{}
34 | headers.Add("Authorization", "Bearer "+string(gw.Token))
35 | return headers
36 | }
37 | log.Warn("Sealer API Token not set and requested, capabilities might be limited.")
38 | return nil
39 | }
40 |
41 | func newDefaultGatewayNode() *GatewayNode {
42 | return &GatewayNode{
43 | ListenAPI: []string{},
44 | Token: "",
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/node/config/load.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "os"
8 |
9 | "github.com/BurntSushi/toml"
10 | )
11 |
12 | // FromFile loads config from a specified file overriding defaults specified in
13 | // the def parameter. If file does not exist or is empty defaults are assumed.
14 | func FromFile(path string, def interface{}) (interface{}, error) {
15 | file, err := os.Open(path)
16 | switch {
17 | case os.IsNotExist(err):
18 | return def, nil
19 | case err != nil:
20 | return nil, err
21 | }
22 |
23 | defer file.Close() //nolint:errcheck // The file is RO
24 | return FromReader(file, def)
25 | }
26 |
27 | // FromReader loads config from a reader instance.
28 | func FromReader(reader io.Reader, def interface{}) (interface{}, error) {
29 | cfg := def
30 | _, err := toml.NewDecoder(reader).Decode(cfg)
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | return cfg, nil
36 | }
37 |
38 | func ConfigComment(t interface{}) ([]byte, error) {
39 | buf := new(bytes.Buffer)
40 | _, _ = buf.WriteString("# Default config:\n")
41 | e := toml.NewEncoder(buf)
42 | if err := e.Encode(t); err != nil {
43 | return nil, fmt.Errorf("encoding config: %w", err)
44 | }
45 | b := buf.Bytes()
46 | b = bytes.ReplaceAll(b, []byte("\n"), []byte("\n#"))
47 | b = bytes.ReplaceAll(b, []byte("#["), []byte("["))
48 | return b, nil
49 | }
50 |
--------------------------------------------------------------------------------
/node/config/load_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "bytes"
5 | "os"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestDecodeNothing(t *testing.T) {
12 | assert := assert.New(t)
13 |
14 | {
15 | cfg, err := FromFile(os.DevNull, DefaultMinerConfig())
16 | assert.Nil(err, "error should be nil")
17 | assert.Equal(DefaultMinerConfig(), cfg,
18 | "config from empty file should be the same as default")
19 | }
20 |
21 | {
22 | cfg, err := FromFile("./does-not-exist.toml", DefaultMinerConfig())
23 | assert.Nil(err, "error should be nil")
24 | assert.Equal(DefaultMinerConfig(), cfg,
25 | "config from not exisiting file should be the same as default")
26 | }
27 | }
28 |
29 | func TestParitalConfig(t *testing.T) {
30 | assert := assert.New(t)
31 | cfgString := `
32 | [FullNode]
33 | `
34 | expected := DefaultMinerConfig()
35 |
36 | {
37 | cfg, err := FromReader(bytes.NewReader([]byte(cfgString)), DefaultMinerConfig())
38 | assert.NoError(err, "error should be nil")
39 | assert.Equal(expected, cfg,
40 | "config from reader should contain changes")
41 | }
42 |
43 | {
44 | f, err := os.CreateTemp("", "config-*.toml")
45 | fname := f.Name()
46 |
47 | assert.NoError(err, "tmp file shold not error")
48 | _, err = f.WriteString(cfgString)
49 | assert.NoError(err, "writing to tmp file should not error")
50 | err = f.Close()
51 | assert.NoError(err, "closing tmp file should not error")
52 | defer os.Remove(fname) //nolint:errcheck
53 |
54 | cfg, err := FromFile(fname, DefaultMinerConfig())
55 | assert.Nil(err, "error should be nil")
56 | assert.Equal(expected, cfg,
57 | "config from reader should contain changes")
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/node/config/migrate/def_nv170.go:
--------------------------------------------------------------------------------
1 | package migrate
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/ipfs-force-community/metrics"
7 |
8 | "github.com/ipfs-force-community/sophon-miner/node/config"
9 | )
10 |
11 | type MySQLConfig struct {
12 | Conn string
13 | MaxOpenConn int // 100
14 | MaxIdleConn int // 10
15 | ConnMaxLifeTime time.Duration // 60(s)
16 | Debug bool
17 | }
18 |
19 | type SlashFilterConfig struct {
20 | Type string
21 | MySQL MySQLConfig
22 | }
23 |
24 | type MinerConfig struct {
25 | FullNode *config.APIInfo
26 | Gateway *config.GatewayNode
27 | Auth *config.APIInfo
28 |
29 | SlashFilter *SlashFilterConfig
30 |
31 | Tracing *metrics.TraceConfig
32 | Metrics *metrics.MetricsConfig
33 | }
34 |
35 | func (cfgV170 *MinerConfig) ToMinerConfig(cfg *config.MinerConfig) {
36 | cfg.FullNode = cfgV170.FullNode
37 | cfg.Gateway = cfgV170.Gateway
38 | cfg.Auth = cfgV170.Auth
39 | cfg.SlashFilter.Type = cfgV170.SlashFilter.Type
40 | cfg.SlashFilter.MySQL.Conn = cfgV170.SlashFilter.MySQL.Conn
41 | cfg.SlashFilter.MySQL.MaxOpenConn = cfgV170.SlashFilter.MySQL.MaxOpenConn
42 | cfg.SlashFilter.MySQL.MaxIdleConn = cfgV170.SlashFilter.MySQL.MaxIdleConn
43 | cfg.SlashFilter.MySQL.ConnMaxLifeTime = config.Duration(cfgV170.SlashFilter.MySQL.ConnMaxLifeTime)
44 | cfg.SlashFilter.MySQL.Debug = cfgV170.SlashFilter.MySQL.Debug
45 | cfg.Tracing = cfgV170.Tracing
46 | cfg.Metrics = cfgV170.Metrics
47 | }
48 |
--------------------------------------------------------------------------------
/node/impl/common/common.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/google/uuid"
7 | logging "github.com/ipfs/go-log/v2"
8 | "go.uber.org/fx"
9 |
10 | "github.com/ipfs-force-community/sophon-miner/api"
11 | "github.com/ipfs-force-community/sophon-miner/build"
12 | "github.com/ipfs-force-community/sophon-miner/types"
13 |
14 | sharedTypes "github.com/filecoin-project/venus/venus-shared/types"
15 | )
16 |
17 | var session = uuid.New()
18 |
19 | type CommonAPI struct {
20 | fx.In
21 |
22 | ShutdownChan types.ShutdownChan
23 | }
24 |
25 | var apiVersion = sharedTypes.NewVer(1, 2, 0)
26 |
27 | func (a *CommonAPI) Version(context.Context) (sharedTypes.Version, error) {
28 | return sharedTypes.Version{
29 | Version: build.UserVersion(),
30 | APIVersion: apiVersion,
31 | }, nil
32 | }
33 |
34 | func (a *CommonAPI) LogList(context.Context) ([]string, error) {
35 | return logging.GetSubsystems(), nil
36 | }
37 |
38 | func (a *CommonAPI) LogSetLevel(ctx context.Context, subsystem, level string) error {
39 | return logging.SetLogLevel(subsystem, level)
40 | }
41 |
42 | func (a *CommonAPI) Shutdown(ctx context.Context) error {
43 | a.ShutdownChan <- struct{}{}
44 | return nil
45 | }
46 |
47 | func (a *CommonAPI) Session(ctx context.Context) (uuid.UUID, error) {
48 | return session, nil
49 | }
50 |
51 | func (a *CommonAPI) Closing(ctx context.Context) (<-chan struct{}, error) {
52 | return make(chan struct{}), nil // relies on jsonrpc closing
53 | }
54 |
55 | var _ api.Common = &CommonAPI{}
56 |
--------------------------------------------------------------------------------
/node/impl/miner.go:
--------------------------------------------------------------------------------
1 | package impl
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/filecoin-project/go-address"
7 | "github.com/filecoin-project/go-state-types/abi"
8 | "github.com/ipfs-force-community/sophon-auth/jwtclient"
9 |
10 | "github.com/ipfs-force-community/sophon-miner/api"
11 | "github.com/ipfs-force-community/sophon-miner/miner"
12 | "github.com/ipfs-force-community/sophon-miner/node/impl/common"
13 | "github.com/ipfs-force-community/sophon-miner/types"
14 | )
15 |
16 | type MinerAPI struct {
17 | common.CommonAPI
18 | miner.MiningAPI
19 |
20 | AuthClient jwtclient.IAuthClient
21 | }
22 |
23 | var _ api.MinerAPI = &MinerAPI{}
24 |
25 | func (m *MinerAPI) UpdateAddress(ctx context.Context, skip int64, limit int64) ([]types.MinerInfo, error) {
26 | return m.MiningAPI.UpdateAddress(ctx, skip, limit)
27 | }
28 |
29 | func (m *MinerAPI) ListAddress(ctx context.Context) ([]types.MinerInfo, error) {
30 | mis, err := m.MiningAPI.ListAddress(ctx)
31 | if err != nil {
32 | return nil, err
33 | }
34 | ret := filter(mis, func(mi types.MinerInfo) bool {
35 | return jwtclient.CheckPermissionByMiner(ctx, m.AuthClient, mi.Addr) == nil
36 | })
37 |
38 | return ret, nil
39 | }
40 |
41 | func (m *MinerAPI) StatesForMining(ctx context.Context, addrs []address.Address) ([]types.MinerState, error) {
42 | addrsAllowed := filter(addrs, func(addr address.Address) bool {
43 | return jwtclient.CheckPermissionByMiner(ctx, m.AuthClient, addr) == nil
44 | })
45 | return m.MiningAPI.StatesForMining(ctx, addrsAllowed)
46 | }
47 |
48 | func (m *MinerAPI) CountWinners(ctx context.Context, addrs []address.Address, start abi.ChainEpoch, end abi.ChainEpoch) ([]types.CountWinners, error) {
49 | addrsAllowed := filter(addrs, func(addr address.Address) bool {
50 | return jwtclient.CheckPermissionByMiner(ctx, m.AuthClient, addr) == nil
51 | })
52 | return m.MiningAPI.CountWinners(ctx, addrsAllowed, start, end)
53 | }
54 |
55 | func (m *MinerAPI) ListBlocks(ctx context.Context, params *types.BlocksQueryParams) ([]types.MinedBlock, error) {
56 | addrsAllowed := filter(params.Miners, func(addr address.Address) bool {
57 | return jwtclient.CheckPermissionByMiner(ctx, m.AuthClient, addr) == nil
58 | })
59 | params.Miners = addrsAllowed
60 | return m.MiningAPI.ListBlocks(ctx, params)
61 | }
62 |
63 | func (m *MinerAPI) WarmupForMiner(ctx context.Context, maddr address.Address) error {
64 | if err := jwtclient.CheckPermissionByMiner(ctx, m.AuthClient, maddr); err != nil {
65 | return err
66 | }
67 | return m.MiningAPI.WarmupForMiner(ctx, maddr)
68 | }
69 |
70 | func (m *MinerAPI) Start(ctx context.Context, addrs []address.Address) error {
71 | addrsAllowed := filter(addrs, func(addr address.Address) bool {
72 | return jwtclient.CheckPermissionByMiner(ctx, m.AuthClient, addr) == nil
73 | })
74 | return m.MiningAPI.ManualStart(ctx, addrsAllowed)
75 | }
76 |
77 | func (m *MinerAPI) Stop(ctx context.Context, addrs []address.Address) error {
78 | addrsAllowed := filter(addrs, func(addr address.Address) bool {
79 | return jwtclient.CheckPermissionByMiner(ctx, m.AuthClient, addr) == nil
80 | })
81 | return m.MiningAPI.ManualStop(ctx, addrsAllowed)
82 | }
83 |
84 | func (m *MinerAPI) QueryRecord(ctx context.Context, params *types.QueryRecordParams) ([]map[string]string, error) {
85 | if err := jwtclient.CheckPermissionByMiner(ctx, m.AuthClient, params.Miner); err != nil {
86 | return nil, err
87 | }
88 | return m.MiningAPI.QueryRecord(ctx, params)
89 | }
90 |
91 | func filter[T any](src []T, pass func(T) bool) []T {
92 | ret := make([]T, 0)
93 | for _, addr := range src {
94 | if pass(addr) {
95 | ret = append(ret, addr)
96 | }
97 | }
98 | return ret
99 | }
100 |
--------------------------------------------------------------------------------
/node/modules/helpers/helpers.go:
--------------------------------------------------------------------------------
1 | package helpers
2 |
3 | import (
4 | "context"
5 |
6 | "go.uber.org/fx"
7 | )
8 |
9 | // MetricsCtx is a context wrapper with metrics
10 | type MetricsCtx context.Context
11 |
12 | // LifecycleCtx creates a context which will be cancelled when lifecycle stops
13 | //
14 | // This is a hack which we need because most of our services use contexts in a
15 | // wrong way
16 | func LifecycleCtx(mctx MetricsCtx, lc fx.Lifecycle) context.Context {
17 | ctx, cancel := context.WithCancel(mctx)
18 | lc.Append(fx.Hook{
19 | OnStop: func(_ context.Context) error {
20 | cancel()
21 | return nil
22 | },
23 | })
24 | return ctx
25 | }
26 |
--------------------------------------------------------------------------------
/node/modules/mine-recorder/inner_recorder.go:
--------------------------------------------------------------------------------
1 | package minerecorder
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 |
8 | "github.com/filecoin-project/go-address"
9 | "github.com/filecoin-project/go-state-types/abi"
10 | "github.com/ipfs/go-datastore"
11 | )
12 |
13 | var (
14 | ErrDatastoreNotSet = fmt.Errorf("database not set")
15 | ErrRecorderDisabled = fmt.Errorf("recorder disabled")
16 | )
17 | var innerRecorder Recorder
18 | var enable = false
19 |
20 | func SetDatastore(ds datastore.Datastore) {
21 | innerRecorder = NewDefaultRecorder(ds)
22 | enable = true
23 | }
24 |
25 | func checkAvailable() error {
26 | if !enable {
27 | return ErrRecorderDisabled
28 | }
29 | return nil
30 | }
31 |
32 | func Record(ctx context.Context, miner address.Address, epoch abi.ChainEpoch, r Records) error {
33 | err := checkAvailable()
34 | if err != nil {
35 | return err
36 | }
37 | return innerRecorder.Record(ctx, miner, epoch, r)
38 | }
39 |
40 | func Query(ctx context.Context, miner address.Address, epoch abi.ChainEpoch, limit uint) ([]Records, error) {
41 | err := checkAvailable()
42 | if err != nil {
43 | return nil, err
44 | }
45 | return innerRecorder.Query(ctx, miner, epoch, limit)
46 | }
47 |
48 | type subRecorder struct {
49 | miner address.Address
50 | epoch abi.ChainEpoch
51 | }
52 |
53 | func (s *subRecorder) Record(ctx context.Context, r Records) {
54 | err := checkAvailable()
55 | if errors.Is(err, ErrRecorderDisabled) {
56 | log.Debugf("recorder disabled, skip record")
57 | return
58 | } else if err != nil {
59 | log.Warnf("record failed: %s", err.Error())
60 | }
61 | err = innerRecorder.Record(ctx, s.miner, s.epoch, r)
62 | if err != nil {
63 | log.Warnf("record failed: %s", err.Error())
64 | }
65 | }
66 |
67 | func Sub(miner address.Address, epoch abi.ChainEpoch) SubRecorder {
68 | return &subRecorder{
69 | miner: miner,
70 | epoch: epoch,
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/node/modules/mine-recorder/mod.go:
--------------------------------------------------------------------------------
1 | package minerecorder
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/binary"
7 | "encoding/json"
8 | "errors"
9 | "fmt"
10 | "sync"
11 |
12 | logging "github.com/ipfs/go-log/v2"
13 |
14 | "github.com/filecoin-project/go-address"
15 | "github.com/filecoin-project/go-state-types/abi"
16 | "github.com/ipfs/go-datastore"
17 | "github.com/ipfs/go-datastore/namespace"
18 | "github.com/ipfs/go-datastore/query"
19 | )
20 |
21 | var log = logging.Logger("mine-recorder")
22 |
23 | var (
24 | // MaxRecordPerQuery is the max record can be query in one time
25 | MaxRecordPerQuery uint = 288
26 | // ExpireEpoch is the num of epoch that record will be expired, default is 7 * 2880 epoch, about 7 days
27 | ExpireEpoch = abi.ChainEpoch(7 * 2880)
28 |
29 | DatastoreNamespaceKey = datastore.NewKey("/mine-record")
30 | )
31 |
32 | var (
33 | ErrorRecordNotFound = datastore.ErrNotFound
34 | ErrorExceedMaxRecordPerQuery = fmt.Errorf("query exceed MaxRecordPerQuery(%d)", MaxRecordPerQuery)
35 | )
36 |
37 | var (
38 | metaMinEpochKey = datastore.NewKey("/index/min-epoch")
39 | )
40 |
41 | type Recorder interface {
42 | Record(ctx context.Context, miner address.Address, epoch abi.ChainEpoch, r Records) error
43 | Query(ctx context.Context, miner address.Address, from abi.ChainEpoch, limit uint) ([]Records, error)
44 | }
45 |
46 | type SubRecorder interface {
47 | Record(ctx context.Context, r Records)
48 | }
49 |
50 | type Records = map[string]string
51 |
52 | func key(miner address.Address, epoch abi.ChainEpoch) datastore.Key {
53 | return datastore.NewKey("/" + epoch.String() + "/" + miner.String())
54 | }
55 |
56 | type DefaultRecorder struct {
57 | ds datastore.Datastore
58 | mut sync.RWMutex
59 | latestRecordEpoch abi.ChainEpoch
60 | }
61 |
62 | var _ Recorder = (*DefaultRecorder)(nil)
63 |
64 | func NewDefaultRecorder(ds datastore.Datastore) *DefaultRecorder {
65 | return &DefaultRecorder{
66 | ds: namespace.Wrap(ds, DatastoreNamespaceKey),
67 | }
68 | }
69 |
70 | func (d *DefaultRecorder) Record(ctx context.Context, miner address.Address, epoch abi.ChainEpoch, r Records) error {
71 | before, err := d.get(ctx, miner, epoch)
72 | if err != nil && !errors.Is(err, ErrorRecordNotFound) {
73 | return err
74 | }
75 |
76 | r = coverMap(before, r)
77 |
78 | err = d.put(ctx, miner, epoch, r)
79 | if err != nil {
80 | return err
81 | }
82 |
83 | err = d.cleanExpire(ctx, epoch)
84 | if err != nil {
85 | return err
86 | }
87 | return nil
88 | }
89 |
90 | func (d *DefaultRecorder) get(ctx context.Context, miner address.Address, epoch abi.ChainEpoch) (Records, error) {
91 | key := key(miner, epoch)
92 | val, err := d.ds.Get(ctx, key)
93 | if err != nil {
94 | return nil, fmt.Errorf("get record (%s) fail: %w", key, err)
95 | }
96 | var record Records
97 | err = json.Unmarshal(val, &record)
98 | if err != nil {
99 | return nil, err
100 | }
101 | return record, nil
102 | }
103 |
104 | func (d *DefaultRecorder) put(ctx context.Context, miner address.Address, epoch abi.ChainEpoch, r Records) error {
105 | key := key(miner, epoch)
106 | val, err := json.Marshal(&r)
107 | if err != nil {
108 | return err
109 | }
110 | err = d.ds.Put(ctx, key, val)
111 | if err != nil {
112 | return err
113 | }
114 | return nil
115 | }
116 |
117 | func (d *DefaultRecorder) Query(ctx context.Context, miner address.Address, from abi.ChainEpoch, limit uint) ([]Records, error) {
118 | if limit > MaxRecordPerQuery {
119 | return nil, ErrorExceedMaxRecordPerQuery
120 | }
121 | if limit == 0 {
122 | limit = 1
123 | }
124 | ret := make([]Records, 0, limit)
125 | to := from + abi.ChainEpoch(limit)
126 | for ; from < to; from++ {
127 | r, err := d.get(ctx, miner, from)
128 | if errors.Is(err, ErrorRecordNotFound) {
129 | // ignore record not found
130 | log.Warnf("query record: %s on %d : %s", miner, from, err.Error())
131 | continue
132 | } else if err != nil {
133 | return nil, err
134 | }
135 | covered := coverMap(r, Records{"miner": miner.String(), "epoch": from.String()})
136 | ret = append(ret, covered)
137 | }
138 |
139 | if len(ret) != int(limit) {
140 | log.Infof("query record: %s from %d to %d ,found %d ", miner, from-abi.ChainEpoch(limit), from, len(ret))
141 | } else {
142 | log.Debugf("query record: %s from %d to %d ,found %d ", miner, from-abi.ChainEpoch(limit), limit, len(ret))
143 | }
144 | return ret, nil
145 | }
146 |
147 | func (d *DefaultRecorder) cleanExpire(ctx context.Context, epoch abi.ChainEpoch) error {
148 | d.mut.RLock()
149 | latestRecordEpoch := d.latestRecordEpoch
150 | d.mut.RUnlock()
151 |
152 | if epoch > latestRecordEpoch {
153 | needClean := false
154 | d.mut.Lock()
155 | // double check, prevent latestRecordEpoch was updated by other goroutine before get write lock
156 | if epoch > d.latestRecordEpoch {
157 | d.latestRecordEpoch = epoch
158 | needClean = true
159 | }
160 | d.mut.Unlock()
161 |
162 | if needClean {
163 | deadline := epoch - ExpireEpoch
164 | err := d.cleanBefore(ctx, deadline)
165 | if err != nil {
166 | return err
167 | }
168 | }
169 | }
170 | return nil
171 | }
172 |
173 | func (d *DefaultRecorder) cleanBefore(ctx context.Context, deadline abi.ChainEpoch) error {
174 | minEpochInByte, err := d.ds.Get(ctx, metaMinEpochKey)
175 | if errors.Is(err, datastore.ErrNotFound) {
176 | // no min epoch record
177 | minEpochInByte = intoBytes(deadline)
178 | err = d.ds.Put(ctx, metaMinEpochKey, minEpochInByte)
179 | return err
180 | } else if err != nil {
181 | return fmt.Errorf("get min epoch: %w", err)
182 | }
183 |
184 | var minEpoch abi.ChainEpoch
185 | fromBytes(minEpochInByte, &minEpoch)
186 |
187 | if deadline > minEpoch {
188 | // delete record range [minEpoch, deadline)
189 | for i := minEpoch; i < deadline; i++ {
190 | res, err := d.ds.Query(ctx, query.Query{
191 | Prefix: "/" + i.String(),
192 | KeysOnly: true,
193 | })
194 | if err != nil {
195 | return fmt.Errorf("query record: %w", err)
196 | }
197 | for r := range res.Next() {
198 | if r.Error != nil {
199 | return fmt.Errorf("query record: %w", r.Error)
200 | }
201 | err := d.ds.Delete(ctx, datastore.NewKey(r.Entry.Key))
202 | if err != nil {
203 | return fmt.Errorf("delete record: %w", err)
204 | }
205 | }
206 | }
207 |
208 | deadlineInByte := intoBytes(deadline)
209 | err = d.ds.Put(ctx, metaMinEpochKey, deadlineInByte)
210 | if err != nil {
211 | return err
212 | }
213 | }
214 | return nil
215 | }
216 |
217 | func intoBytes(v any) []byte {
218 | buf := bytes.NewBuffer([]byte{})
219 | binary.Write(buf, binary.BigEndian, v) // nolint: errcheck
220 | return buf.Bytes()
221 | }
222 |
223 | func fromBytes(b []byte, v any) {
224 | buf := bytes.NewBuffer(b)
225 | binary.Read(buf, binary.BigEndian, v) // nolint: errcheck
226 | }
227 |
228 | // mergeMap cover src onto dst, the key in dst will overwrite the key in src
229 | func coverMap[K comparable, V any](before, after map[K]V) map[K]V {
230 | newMap := make(map[K]V)
231 | for k, v := range before {
232 | newMap[k] = v
233 | }
234 | for k, v := range after {
235 | newMap[k] = v
236 | }
237 | return newMap
238 | }
239 |
--------------------------------------------------------------------------------
/node/modules/mine-recorder/record_test.go:
--------------------------------------------------------------------------------
1 | package minerecorder
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/filecoin-project/go-address"
8 | "github.com/filecoin-project/go-state-types/abi"
9 | "github.com/ipfs/go-datastore"
10 | leveldb "github.com/ipfs/go-ds-leveldb"
11 | "github.com/stretchr/testify/require"
12 | )
13 |
14 | var (
15 | RecordExample = make(map[string]string)
16 | )
17 |
18 | func TestDefaultRecorder(t *testing.T) {
19 | ctx := context.Background()
20 |
21 | t.Run("record and get", func(t *testing.T) {
22 | db := createDatastore(t)
23 | r := NewDefaultRecorder(db)
24 | defer db.Close()
25 |
26 | err := r.Record(ctx, newIDAddress(1), abi.ChainEpoch(0), RecordExample)
27 | require.NoError(t, err)
28 | ret, err := r.get(ctx, newIDAddress(1), abi.ChainEpoch(0))
29 | require.NoError(t, err)
30 | require.Equal(t, RecordExample, ret)
31 | })
32 |
33 | t.Run("query record not exist", func(t *testing.T) {
34 | db := createDatastore(t)
35 | r := NewDefaultRecorder(db)
36 | defer db.Close()
37 |
38 | _, err := r.get(ctx, newIDAddress(1), abi.ChainEpoch(9))
39 | require.ErrorIs(t, err, ErrorRecordNotFound)
40 |
41 | err = r.Record(ctx, newIDAddress(1), abi.ChainEpoch(9), RecordExample)
42 | require.NoError(t, err)
43 | err = r.Record(ctx, newIDAddress(1), abi.ChainEpoch(10), RecordExample)
44 | require.NoError(t, err)
45 | err = r.Record(ctx, newIDAddress(1), abi.ChainEpoch(11), RecordExample)
46 | require.NoError(t, err)
47 | err = r.Record(ctx, newIDAddress(1), abi.ChainEpoch(14), RecordExample)
48 | require.NoError(t, err)
49 |
50 | rets, err := r.Query(ctx, newIDAddress(1), abi.ChainEpoch(5), 10)
51 | require.NoError(t, err)
52 | require.Equal(t, 4, len(rets))
53 | })
54 |
55 | t.Run("query exceed MaxRecordPerQuery", func(t *testing.T) {
56 | db := createDatastore(t)
57 | r := NewDefaultRecorder(db)
58 | defer db.Close()
59 |
60 | _, err := r.Query(ctx, newIDAddress(1), abi.ChainEpoch(0), MaxRecordPerQuery+1)
61 | require.ErrorIs(t, err, ErrorExceedMaxRecordPerQuery)
62 | })
63 |
64 | t.Run("auto clean", func(t *testing.T) {
65 | ExpireEpoch = abi.ChainEpoch(10)
66 | db := createDatastore(t)
67 | r := NewDefaultRecorder(db)
68 | defer db.Close()
69 |
70 | // pre fill
71 | err := r.Record(ctx, newIDAddress(1), abi.ChainEpoch(0), RecordExample)
72 | require.NoError(t, err)
73 | err = r.Record(ctx, newIDAddress(2), abi.ChainEpoch(0), RecordExample)
74 | require.NoError(t, err)
75 |
76 | ret, err := r.get(ctx, newIDAddress(1), abi.ChainEpoch(0))
77 | require.NoError(t, err)
78 | require.Equal(t, RecordExample, ret)
79 |
80 | // approach expiration
81 | err = r.Record(ctx, newIDAddress(1), abi.ChainEpoch(ExpireEpoch), RecordExample)
82 | require.NoError(t, err)
83 |
84 | ret, err = r.get(ctx, newIDAddress(1), abi.ChainEpoch(0))
85 | require.NoError(t, err)
86 | require.Equal(t, RecordExample, ret)
87 |
88 | // exceed expiration
89 | err = r.Record(ctx, newIDAddress(1), abi.ChainEpoch(ExpireEpoch+1), RecordExample)
90 | require.NoError(t, err)
91 |
92 | _, err = r.get(ctx, newIDAddress(1), abi.ChainEpoch(0))
93 | require.ErrorIs(t, err, ErrorRecordNotFound)
94 | _, err = r.get(ctx, newIDAddress(2), abi.ChainEpoch(0))
95 | require.ErrorIs(t, err, ErrorRecordNotFound)
96 |
97 | ret, err = r.get(ctx, newIDAddress(1), abi.ChainEpoch(ExpireEpoch))
98 | require.NoError(t, err)
99 | require.Equal(t, RecordExample, ret)
100 | })
101 |
102 | t.Run("sub recorder", func(t *testing.T) {
103 | db := createDatastore(t)
104 | SetDatastore(db)
105 |
106 | rcd := Sub(newIDAddress(1), abi.ChainEpoch(0))
107 | rcd.Record(ctx, Records{"key": "val"})
108 |
109 | ret, err := Query(ctx, newIDAddress(1), abi.ChainEpoch(0), 1)
110 | require.NoError(t, err)
111 | require.Equal(t, 1, len(ret))
112 | require.Equal(t, "val", ret[0]["key"])
113 | })
114 | }
115 |
116 | func TestCoverMap(t *testing.T) {
117 | t.Run("cover map", func(t *testing.T) {
118 | before := Records{
119 | "key1": "val1",
120 | "key2": "val2",
121 | }
122 | after := Records{
123 | "key1": "val4",
124 | "key3": "val3",
125 | }
126 | ret := coverMap(before, after)
127 | require.Equal(t, Records{
128 | "key1": "val4",
129 | "key2": "val2",
130 | "key3": "val3",
131 | }, ret)
132 | })
133 | }
134 |
135 | func createDatastore(t testing.TB) datastore.Batching {
136 | path := t.TempDir() + "/leveldb"
137 | db, err := leveldb.NewDatastore(path, nil)
138 | require.NoError(t, err)
139 | return db
140 | }
141 |
142 | func newIDAddress(id uint64) address.Address {
143 | ret, err := address.NewIDAddress(id)
144 | if err != nil {
145 | panic("create id address fail")
146 | }
147 | return ret
148 | }
149 |
150 | func BenchmarkQuery(b *testing.B) {
151 | ctx := context.Background()
152 | db := createDatastore(b)
153 | r := NewDefaultRecorder(db)
154 | defer db.Close()
155 |
156 | for i := 0; i < 1000*000*1000; i++ {
157 | err := r.Record(ctx, newIDAddress(1), abi.ChainEpoch(i), RecordExample)
158 | require.NoError(b, err)
159 | }
160 |
161 | b.ResetTimer()
162 | for i := 0; i < b.N; i++ {
163 | _, err := r.Query(ctx, newIDAddress(1), abi.ChainEpoch(0), 2000)
164 | require.NoError(b, err)
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/node/modules/miner-manager/api.go:
--------------------------------------------------------------------------------
1 | package miner_manager
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/filecoin-project/go-address"
7 |
8 | "github.com/ipfs-force-community/sophon-miner/types"
9 | )
10 |
11 | //go:generate go run github.com/golang/mock/mockgen -destination=mock/miner_manager.go -package=mock . MinerManageAPI
12 |
13 | type MinerManageAPI interface {
14 | Has(ctx context.Context, mAddr address.Address) bool
15 | Get(ctx context.Context, mAddr address.Address) (*types.MinerInfo, error)
16 | IsOpenMining(ctx context.Context, mAddr address.Address) bool
17 | OpenMining(ctx context.Context, mAddr address.Address) (*types.MinerInfo, error)
18 | CloseMining(ctx context.Context, mAddr address.Address) error
19 | List(ctx context.Context) (map[address.Address]*types.MinerInfo, error)
20 | Update(ctx context.Context, skip, limit int64) (map[address.Address]*types.MinerInfo, error)
21 | }
22 |
--------------------------------------------------------------------------------
/node/modules/miner-manager/auth_manager.go:
--------------------------------------------------------------------------------
1 | package miner_manager
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "sync"
7 |
8 | "github.com/filecoin-project/go-address"
9 | logging "github.com/ipfs/go-log/v2"
10 |
11 | "github.com/ipfs-force-community/sophon-auth/jwtclient"
12 |
13 | "github.com/ipfs-force-community/sophon-miner/types"
14 | )
15 |
16 | const CoMinersLimit = 20000
17 |
18 | var (
19 | ErrNotFound = fmt.Errorf("not found")
20 | )
21 |
22 | var log = logging.Logger("auth-miners")
23 |
24 | type MinerManage struct {
25 | authClient jwtclient.IAuthClient
26 |
27 | miners map[address.Address]*types.MinerInfo
28 | lk sync.Mutex
29 | }
30 |
31 | func NewVenusAuth(url, token string) func() (jwtclient.IAuthClient, error) {
32 | return func() (jwtclient.IAuthClient, error) {
33 | return jwtclient.NewAuthClient(url, token)
34 | }
35 | }
36 |
37 | func NewMinerManager(authClient jwtclient.IAuthClient) (MinerManageAPI, error) {
38 | m := &MinerManage{authClient: authClient}
39 | _, err := m.Update(context.TODO(), 0, 0)
40 | if err != nil {
41 | return nil, err
42 | }
43 |
44 | return m, nil
45 | }
46 |
47 | func (m *MinerManage) Has(ctx context.Context, mAddr address.Address) bool {
48 | m.lk.Lock()
49 | defer m.lk.Unlock()
50 |
51 | _, ok := m.miners[mAddr]
52 | return ok
53 | }
54 |
55 | func (m *MinerManage) Get(ctx context.Context, mAddr address.Address) (*types.MinerInfo, error) {
56 | m.lk.Lock()
57 | defer m.lk.Unlock()
58 |
59 | if _, ok := m.miners[mAddr]; ok {
60 | return m.miners[mAddr], nil
61 | }
62 |
63 | return nil, ErrNotFound
64 | }
65 |
66 | func (m *MinerManage) IsOpenMining(ctx context.Context, mAddr address.Address) bool {
67 | m.lk.Lock()
68 | defer m.lk.Unlock()
69 |
70 | if _, ok := m.miners[mAddr]; ok {
71 | return m.miners[mAddr].OpenMining
72 | }
73 |
74 | return false
75 | }
76 |
77 | func (m *MinerManage) OpenMining(ctx context.Context, mAddr address.Address) (*types.MinerInfo, error) {
78 | m.lk.Lock()
79 | defer m.lk.Unlock()
80 |
81 | if minerInfo, ok := m.miners[mAddr]; ok {
82 | _, err := m.authClient.UpsertMiner(ctx, minerInfo.Name, minerInfo.Addr.String(), true)
83 | if err != nil {
84 | return nil, err
85 | }
86 | minerInfo.OpenMining = true
87 | return minerInfo, nil
88 | }
89 |
90 | return nil, ErrNotFound
91 | }
92 |
93 | func (m *MinerManage) CloseMining(ctx context.Context, mAddr address.Address) error {
94 | m.lk.Lock()
95 | defer m.lk.Unlock()
96 |
97 | if minerInfo, ok := m.miners[mAddr]; ok {
98 | _, err := m.authClient.UpsertMiner(ctx, minerInfo.Name, minerInfo.Addr.String(), false)
99 | if err != nil {
100 | return err
101 | }
102 | minerInfo.OpenMining = false
103 | return nil
104 | }
105 |
106 | return ErrNotFound
107 | }
108 |
109 | func (m *MinerManage) List(ctx context.Context) (map[address.Address]*types.MinerInfo, error) {
110 | m.lk.Lock()
111 | defer m.lk.Unlock()
112 |
113 | return m.miners, nil
114 | }
115 |
116 | func (m *MinerManage) Update(ctx context.Context, skip, limit int64) (map[address.Address]*types.MinerInfo, error) {
117 | m.lk.Lock()
118 | defer m.lk.Unlock()
119 |
120 | if limit == 0 {
121 | limit = CoMinersLimit
122 | }
123 |
124 | users, err := m.authClient.ListUsersWithMiners(ctx, skip, limit, 0)
125 | if err != nil {
126 | return nil, err
127 | }
128 |
129 | miners := make(map[address.Address]*types.MinerInfo, 0)
130 | for _, user := range users {
131 | if user.State != 1 {
132 | log.Warnf("user: %s state is disabled, it's miners won't be updated", user.Name)
133 | continue
134 | }
135 |
136 | for _, miner := range user.Miners {
137 | miners[miner.Miner] = &types.MinerInfo{
138 | Addr: miner.Miner,
139 | Id: user.Id,
140 | Name: miner.User,
141 | OpenMining: miner.OpenMining,
142 | }
143 | }
144 | }
145 | m.miners = miners
146 |
147 | return m.miners, nil
148 | }
149 |
150 | var _ MinerManageAPI = &MinerManage{}
151 |
--------------------------------------------------------------------------------
/node/modules/miner-manager/mock/miner_manager.go:
--------------------------------------------------------------------------------
1 | // Code generated by MockGen. DO NOT EDIT.
2 | // Source: github.com/ipfs-force-community/sophon-miner/node/modules/miner-manager (interfaces: MinerManageAPI)
3 |
4 | // Package mock is a generated GoMock package.
5 | package mock
6 |
7 | import (
8 | context "context"
9 | reflect "reflect"
10 |
11 | address "github.com/filecoin-project/go-address"
12 | gomock "github.com/golang/mock/gomock"
13 | types "github.com/ipfs-force-community/sophon-miner/types"
14 | )
15 |
16 | // MockMinerManageAPI is a mock of MinerManageAPI interface.
17 | type MockMinerManageAPI struct {
18 | ctrl *gomock.Controller
19 | recorder *MockMinerManageAPIMockRecorder
20 | }
21 |
22 | // MockMinerManageAPIMockRecorder is the mock recorder for MockMinerManageAPI.
23 | type MockMinerManageAPIMockRecorder struct {
24 | mock *MockMinerManageAPI
25 | }
26 |
27 | // NewMockMinerManageAPI creates a new mock instance.
28 | func NewMockMinerManageAPI(ctrl *gomock.Controller) *MockMinerManageAPI {
29 | mock := &MockMinerManageAPI{ctrl: ctrl}
30 | mock.recorder = &MockMinerManageAPIMockRecorder{mock}
31 | return mock
32 | }
33 |
34 | // EXPECT returns an object that allows the caller to indicate expected use.
35 | func (m *MockMinerManageAPI) EXPECT() *MockMinerManageAPIMockRecorder {
36 | return m.recorder
37 | }
38 |
39 | // CloseMining mocks base method.
40 | func (m *MockMinerManageAPI) CloseMining(arg0 context.Context, arg1 address.Address) error {
41 | m.ctrl.T.Helper()
42 | ret := m.ctrl.Call(m, "CloseMining", arg0, arg1)
43 | ret0, _ := ret[0].(error)
44 | return ret0
45 | }
46 |
47 | // CloseMining indicates an expected call of CloseMining.
48 | func (mr *MockMinerManageAPIMockRecorder) CloseMining(arg0, arg1 interface{}) *gomock.Call {
49 | mr.mock.ctrl.T.Helper()
50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseMining", reflect.TypeOf((*MockMinerManageAPI)(nil).CloseMining), arg0, arg1)
51 | }
52 |
53 | // Get mocks base method.
54 | func (m *MockMinerManageAPI) Get(arg0 context.Context, arg1 address.Address) (*types.MinerInfo, error) {
55 | m.ctrl.T.Helper()
56 | ret := m.ctrl.Call(m, "Get", arg0, arg1)
57 | ret0, _ := ret[0].(*types.MinerInfo)
58 | ret1, _ := ret[1].(error)
59 | return ret0, ret1
60 | }
61 |
62 | // Get indicates an expected call of Get.
63 | func (mr *MockMinerManageAPIMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
64 | mr.mock.ctrl.T.Helper()
65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockMinerManageAPI)(nil).Get), arg0, arg1)
66 | }
67 |
68 | // Has mocks base method.
69 | func (m *MockMinerManageAPI) Has(arg0 context.Context, arg1 address.Address) bool {
70 | m.ctrl.T.Helper()
71 | ret := m.ctrl.Call(m, "Has", arg0, arg1)
72 | ret0, _ := ret[0].(bool)
73 | return ret0
74 | }
75 |
76 | // Has indicates an expected call of Has.
77 | func (mr *MockMinerManageAPIMockRecorder) Has(arg0, arg1 interface{}) *gomock.Call {
78 | mr.mock.ctrl.T.Helper()
79 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockMinerManageAPI)(nil).Has), arg0, arg1)
80 | }
81 |
82 | // IsOpenMining mocks base method.
83 | func (m *MockMinerManageAPI) IsOpenMining(arg0 context.Context, arg1 address.Address) bool {
84 | m.ctrl.T.Helper()
85 | ret := m.ctrl.Call(m, "IsOpenMining", arg0, arg1)
86 | ret0, _ := ret[0].(bool)
87 | return ret0
88 | }
89 |
90 | // IsOpenMining indicates an expected call of IsOpenMining.
91 | func (mr *MockMinerManageAPIMockRecorder) IsOpenMining(arg0, arg1 interface{}) *gomock.Call {
92 | mr.mock.ctrl.T.Helper()
93 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsOpenMining", reflect.TypeOf((*MockMinerManageAPI)(nil).IsOpenMining), arg0, arg1)
94 | }
95 |
96 | // List mocks base method.
97 | func (m *MockMinerManageAPI) List(arg0 context.Context) (map[address.Address]*types.MinerInfo, error) {
98 | m.ctrl.T.Helper()
99 | ret := m.ctrl.Call(m, "List", arg0)
100 | ret0, _ := ret[0].(map[address.Address]*types.MinerInfo)
101 | ret1, _ := ret[1].(error)
102 | return ret0, ret1
103 | }
104 |
105 | // List indicates an expected call of List.
106 | func (mr *MockMinerManageAPIMockRecorder) List(arg0 interface{}) *gomock.Call {
107 | mr.mock.ctrl.T.Helper()
108 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockMinerManageAPI)(nil).List), arg0)
109 | }
110 |
111 | // OpenMining mocks base method.
112 | func (m *MockMinerManageAPI) OpenMining(arg0 context.Context, arg1 address.Address) (*types.MinerInfo, error) {
113 | m.ctrl.T.Helper()
114 | ret := m.ctrl.Call(m, "OpenMining", arg0, arg1)
115 | ret0, _ := ret[0].(*types.MinerInfo)
116 | ret1, _ := ret[1].(error)
117 | return ret0, ret1
118 | }
119 |
120 | // OpenMining indicates an expected call of OpenMining.
121 | func (mr *MockMinerManageAPIMockRecorder) OpenMining(arg0, arg1 interface{}) *gomock.Call {
122 | mr.mock.ctrl.T.Helper()
123 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenMining", reflect.TypeOf((*MockMinerManageAPI)(nil).OpenMining), arg0, arg1)
124 | }
125 |
126 | // Update mocks base method.
127 | func (m *MockMinerManageAPI) Update(arg0 context.Context, arg1, arg2 int64) (map[address.Address]*types.MinerInfo, error) {
128 | m.ctrl.T.Helper()
129 | ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2)
130 | ret0, _ := ret[0].(map[address.Address]*types.MinerInfo)
131 | ret1, _ := ret[1].(error)
132 | return ret0, ret1
133 | }
134 |
135 | // Update indicates an expected call of Update.
136 | func (mr *MockMinerManageAPIMockRecorder) Update(arg0, arg1, arg2 interface{}) *gomock.Call {
137 | mr.mock.ctrl.T.Helper()
138 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockMinerManageAPI)(nil).Update), arg0, arg1, arg2)
139 | }
140 |
--------------------------------------------------------------------------------
/node/modules/miner.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/filecoin-project/venus/pkg/constants"
7 | chainV1API "github.com/filecoin-project/venus/venus-shared/api/chain/v1"
8 | "github.com/ipfs-force-community/sophon-miner/f3participant"
9 | "github.com/ipfs-force-community/sophon-miner/lib/journal"
10 | "github.com/ipfs-force-community/sophon-miner/miner"
11 | "github.com/ipfs-force-community/sophon-miner/node/config"
12 | "github.com/ipfs-force-community/sophon-miner/node/modules/helpers"
13 | minermanager "github.com/ipfs-force-community/sophon-miner/node/modules/miner-manager"
14 | "github.com/ipfs-force-community/sophon-miner/node/modules/slashfilter"
15 | logging "github.com/ipfs/go-log/v2"
16 | "go.uber.org/fx"
17 | )
18 |
19 | var log = logging.Logger("modules/miner")
20 |
21 | func NewMinerProcessor(lc fx.Lifecycle,
22 | mCtx helpers.MetricsCtx,
23 | api chainV1API.FullNode,
24 | cfg *config.MinerConfig,
25 | sfAPI slashfilter.SlashFilterAPI,
26 | minerManager minermanager.MinerManageAPI,
27 | j journal.Journal,
28 | ) (miner.MiningAPI, error) {
29 | ctx := helpers.LifecycleCtx(mCtx, lc)
30 | m, err := miner.NewMiner(ctx, api, cfg, minerManager, sfAPI, j)
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | lc.Append(fx.Hook{
36 | OnStart: func(ctx context.Context) error {
37 | if err := m.Start(ctx); err != nil {
38 | return err
39 | }
40 | return nil
41 | },
42 | OnStop: func(ctx context.Context) error {
43 | return m.Stop(ctx)
44 | },
45 | })
46 |
47 | return m, err
48 | }
49 |
50 | func NewMultiParticipant(lc fx.Lifecycle,
51 | mCtx helpers.MetricsCtx,
52 | cfg *config.MinerConfig,
53 | minerManager minermanager.MinerManageAPI,
54 | ) (*f3participant.MultiParticipant, error) {
55 | if cfg.F3Node == nil || len(cfg.F3Node.Addr) == 0 {
56 | log.Warnf("f3 node is not configured")
57 | return nil, nil
58 | }
59 | if constants.DisableF3 {
60 | log.Warnf("f3 is disabled")
61 | return nil, nil
62 | }
63 |
64 | node, close, err := chainV1API.DialFullNodeRPC(mCtx, cfg.F3Node.Addr, cfg.F3Node.Token, nil)
65 | if err != nil {
66 | return nil, err
67 | }
68 |
69 | mp, err := f3participant.NewMultiParticipant(mCtx, node, minerManager)
70 | if err != nil {
71 | return nil, err
72 | }
73 |
74 | lc.Append(fx.Hook{
75 | OnStart: func(ctx context.Context) error {
76 | return mp.Start(ctx)
77 | },
78 | OnStop: func(ctx context.Context) error {
79 | close()
80 | return mp.Stop(ctx)
81 | },
82 | })
83 |
84 | return mp, nil
85 | }
86 |
--------------------------------------------------------------------------------
/node/modules/services.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "context"
5 |
6 | "go.uber.org/fx"
7 |
8 | "github.com/ipfs-force-community/sophon-miner/lib/journal"
9 | "github.com/ipfs-force-community/sophon-miner/lib/journal/fsjournal"
10 | "github.com/ipfs-force-community/sophon-miner/node/repo"
11 | )
12 |
13 | func OpenFilesystemJournal(lr repo.LockedRepo, lc fx.Lifecycle, disabled journal.DisabledEvents) (journal.Journal, error) {
14 | jrnl, err := fsjournal.OpenFSJournal(lr, disabled)
15 | if err != nil {
16 | return nil, err
17 | }
18 |
19 | lc.Append(fx.Hook{
20 | OnStop: func(_ context.Context) error { return jrnl.Close() },
21 | })
22 |
23 | return jrnl, err
24 | }
25 |
--------------------------------------------------------------------------------
/node/modules/slashfilter/api.go:
--------------------------------------------------------------------------------
1 | package slashfilter
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/filecoin-project/go-state-types/abi"
8 | "github.com/ipfs-force-community/sophon-miner/types"
9 |
10 | vtypes "github.com/filecoin-project/venus/venus-shared/types"
11 | )
12 |
13 | type BlockStoreType string
14 |
15 | const (
16 | Local BlockStoreType = "local"
17 | MySQL BlockStoreType = "mysql"
18 | )
19 |
20 | type SlashFilterAPI interface {
21 | HasBlock(ctx context.Context, bh *vtypes.BlockHeader) (bool, error)
22 | MinedBlock(ctx context.Context, bh *vtypes.BlockHeader, parentEpoch abi.ChainEpoch) error
23 | PutBlock(ctx context.Context, bh *vtypes.BlockHeader, parentEpoch abi.ChainEpoch, t time.Time, state types.StateMining) error
24 | ListBlock(ctx context.Context, params *types.BlocksQueryParams) ([]MinedBlock, error)
25 | }
26 |
--------------------------------------------------------------------------------
/node/modules/slashfilter/local.go:
--------------------------------------------------------------------------------
1 | package slashfilter
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/ipfs/go-cid"
9 | "github.com/ipfs/go-datastore"
10 | "github.com/ipfs/go-datastore/namespace"
11 |
12 | "github.com/filecoin-project/go-state-types/abi"
13 |
14 | vtypes "github.com/filecoin-project/venus/venus-shared/types"
15 |
16 | "github.com/ipfs-force-community/sophon-miner/types"
17 | )
18 |
19 | type localSlashFilter struct {
20 | byEpoch datastore.Datastore // double-fork mining faults, parent-grinding fault
21 | byParents datastore.Datastore // time-offset mining faults
22 | }
23 |
24 | func NewLocal(ds types.MetadataDS) SlashFilterAPI {
25 | return &localSlashFilter{
26 | byEpoch: namespace.Wrap(ds, datastore.NewKey("/slashfilter/epoch")),
27 | byParents: namespace.Wrap(ds, datastore.NewKey("/slashfilter/parents")),
28 | }
29 | }
30 |
31 | func (f *localSlashFilter) HasBlock(ctx context.Context, bh *vtypes.BlockHeader) (bool, error) {
32 | epochKey := datastore.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, bh.Height))
33 |
34 | return f.byEpoch.Has(ctx, epochKey)
35 | }
36 |
37 | func (f *localSlashFilter) PutBlock(ctx context.Context, bh *vtypes.BlockHeader, parentEpoch abi.ChainEpoch, t time.Time, state types.StateMining) error {
38 | // Only successful block generation is recorded locally
39 | if state != types.Success {
40 | return nil
41 | }
42 |
43 | parentsKey := datastore.NewKey(fmt.Sprintf("/%s/%x", bh.Miner, vtypes.NewTipSetKey(bh.Parents...).Bytes()))
44 | if err := f.byParents.Put(ctx, parentsKey, bh.Cid().Bytes()); err != nil {
45 | return fmt.Errorf("putting byEpoch entry: %w", err)
46 | }
47 |
48 | epochKey := datastore.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, bh.Height))
49 | if err := f.byEpoch.Put(ctx, epochKey, bh.Cid().Bytes()); err != nil {
50 | return fmt.Errorf("putting byEpoch entry: %w", err)
51 | }
52 |
53 | return nil
54 | }
55 |
56 | func (f *localSlashFilter) MinedBlock(ctx context.Context, bh *vtypes.BlockHeader, parentEpoch abi.ChainEpoch) error {
57 | // double-fork mining (2 blocks at one epoch) --> HasBlock
58 |
59 | parentsKey := datastore.NewKey(fmt.Sprintf("/%s/%x", bh.Miner, vtypes.NewTipSetKey(bh.Parents...).Bytes()))
60 | {
61 | // time-offset mining faults (2 blocks with the same parents)
62 | if err := checkFault(ctx, f.byParents, parentsKey, bh, "time-offset mining faults"); err != nil {
63 | return err
64 | }
65 | }
66 |
67 | {
68 | // parent-grinding fault (didn't mine on top of our own block)
69 |
70 | // First check if we have mined a block on the parent epoch
71 | parentEpochKey := datastore.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, parentEpoch))
72 | have, err := f.byEpoch.Has(ctx, parentEpochKey)
73 | if err != nil {
74 | return err
75 | }
76 |
77 | if have {
78 | // If we had, make sure it's in our parent tipset
79 | cidb, err := f.byEpoch.Get(ctx, parentEpochKey)
80 | if err != nil {
81 | return fmt.Errorf("getting other block cid: %w", err)
82 | }
83 |
84 | _, parent, err := cid.CidFromBytes(cidb)
85 | if err != nil {
86 | return err
87 | }
88 |
89 | var found bool
90 | for _, c := range bh.Parents {
91 | if c.Equals(parent) {
92 | found = true
93 | }
94 | }
95 |
96 | if !found {
97 | return fmt.Errorf("produced block would trigger 'parent-grinding fault' consensus fault; miner: %s; bh: %s, expected parent: %s", bh.Miner, bh.Cid(), parent)
98 | }
99 | }
100 | }
101 |
102 | return nil
103 | }
104 |
105 | func (f *localSlashFilter) ListBlock(ctx context.Context, params *types.BlocksQueryParams) ([]MinedBlock, error) {
106 | return nil, fmt.Errorf("you are using levelDB, List Block is not supported")
107 | }
108 |
109 | func checkFault(ctx context.Context, t datastore.Datastore, key datastore.Key, bh *vtypes.BlockHeader, faultType string) error {
110 | fault, err := t.Has(ctx, key)
111 | if err != nil {
112 | return err
113 | }
114 |
115 | if fault {
116 | cidb, err := t.Get(ctx, key)
117 | if err != nil {
118 | return fmt.Errorf("getting other block cid: %w", err)
119 | }
120 |
121 | _, other, err := cid.CidFromBytes(cidb)
122 | if err != nil {
123 | return err
124 | }
125 |
126 | if other == bh.Cid() {
127 | return nil
128 | }
129 |
130 | return fmt.Errorf("produced block would trigger '%s' consensus fault; miner: %s; bh: %s, other: %s", faultType, bh.Miner, bh.Cid(), other)
131 | }
132 |
133 | return nil
134 | }
135 |
--------------------------------------------------------------------------------
/node/modules/slashfilter/mock.go:
--------------------------------------------------------------------------------
1 | package slashfilter
2 |
3 | import (
4 | "github.com/ipfs/go-datastore"
5 | "gorm.io/driver/sqlite"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/logger"
8 | )
9 |
10 | func NewMysqlMock() (SlashFilterAPI, *gorm.DB, error) {
11 | db, err := gorm.Open(
12 | sqlite.Open(":memory:"),
13 | &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
14 |
15 | if err != nil {
16 | return nil, nil, err
17 | }
18 |
19 | sqlDb, err := db.DB()
20 | if err != nil {
21 | return nil, nil, err
22 | }
23 |
24 | // mock db work only allow one connection
25 | sqlDb.SetMaxOpenConns(1)
26 |
27 | if err := db.AutoMigrate(&MinedBlock{}); err != nil {
28 | return nil, nil, err
29 | }
30 |
31 | return &mysqlSlashFilter{_db: db}, db, nil
32 | }
33 |
34 | func NewLocalMock() (SlashFilterAPI, datastore.Datastore, error) {
35 | ds := datastore.NewMapDatastore()
36 | return NewLocal(ds), ds, nil
37 | }
38 |
--------------------------------------------------------------------------------
/node/modules/slashfilter/slashfilter_test.go:
--------------------------------------------------------------------------------
1 | // stm: #uinit
2 | package slashfilter
3 |
4 | import (
5 | "context"
6 | "strings"
7 | "testing"
8 | "time"
9 |
10 | "github.com/ipfs-force-community/sophon-miner/types"
11 | "github.com/ipfs/go-datastore"
12 | "github.com/ipfs/go-datastore/query"
13 |
14 | "github.com/filecoin-project/go-address"
15 | "github.com/filecoin-project/go-state-types/abi"
16 | "github.com/ipfs/go-cid"
17 | "github.com/stretchr/testify/require"
18 |
19 | "github.com/filecoin-project/venus/venus-shared/testutil"
20 | venusTypes "github.com/filecoin-project/venus/venus-shared/types"
21 | )
22 |
23 | func wrapper(f func(*testing.T, SlashFilterAPI, func() error), sf SlashFilterAPI, clear func() error) func(t *testing.T) {
24 | return func(t *testing.T) {
25 | f(t, sf, clear)
26 | }
27 | }
28 |
29 | func makeMysqlTestCtx(t *testing.T) (SlashFilterAPI, func() error) {
30 | sf, db, err := NewMysqlMock()
31 | require.NoError(t, err)
32 | clear := func() error {
33 | return db.Delete(&MinedBlock{}, "1 = 1").Error
34 | }
35 | return sf, clear
36 | }
37 |
38 | func makeLocalTestCtx(t *testing.T) (SlashFilterAPI, func() error) {
39 | sf, ds, err := NewLocalMock()
40 | require.NoError(t, err)
41 |
42 | ctx := context.TODO()
43 |
44 | clear := func() error {
45 | rs, err := ds.Query(ctx, query.Query{Prefix: "/"})
46 | if err != nil {
47 | return err
48 | }
49 | for {
50 | res, ok := rs.NextSync()
51 | if !ok {
52 | break
53 | }
54 | if err := ds.Delete(ctx, datastore.NewKey(res.Key)); err != nil {
55 | return err
56 | }
57 | }
58 | return nil
59 | }
60 | return sf, clear
61 | }
62 |
63 | func checkHasBlock(t *testing.T, api SlashFilterAPI, bh *venusTypes.BlockHeader, expect bool) {
64 | ctx := context.TODO()
65 | exists, err := api.HasBlock(ctx, bh)
66 | require.NoError(t, err)
67 | require.Equal(t, exists, expect)
68 | }
69 |
70 | func TestSlashFilter(t *testing.T) {
71 | // stm: @VENUSMINER_NODE_MODULES_AUTH_MANAGER_MINED_BLOCK_001
72 | t.Run("mysql slash filter tests", func(t *testing.T) {
73 | sf, clear := makeMysqlTestCtx(t)
74 | t.Run("put-block", wrapper(testPutBlock, sf, clear))
75 | t.Run("mined-block", wrapper(testMinedBlock, sf, clear))
76 | })
77 | t.Run("local slash filter tests", func(t *testing.T) {
78 | sf, clear := makeLocalTestCtx(t)
79 | t.Run("put-block", wrapper(testPutBlock, sf, clear))
80 | t.Run("mined-block", wrapper(testMinedBlock, sf, clear))
81 | })
82 | }
83 |
84 | func testPutBlock(t *testing.T, sf SlashFilterAPI, clear func() error) {
85 | require.NoError(t, clear())
86 |
87 | const mdata = "f021344"
88 | maddr, err := address.NewFromString(mdata)
89 | require.NoErrorf(t, err, "parse miner address %s", mdata)
90 |
91 | var parents []cid.Cid
92 | testutil.Provide(t, &parents, testutil.WithSliceLen(2))
93 |
94 | mockCid, _ := cid.Parse("bafkqaaa")
95 |
96 | h := abi.ChainEpoch(100)
97 | ph := abi.ChainEpoch(99)
98 |
99 | cases := []struct {
100 | bh *venusTypes.BlockHeader
101 | parentEpoch abi.ChainEpoch
102 | t time.Time
103 | state types.StateMining
104 | }{
105 | {
106 | bh: &venusTypes.BlockHeader{
107 | Parents: parents,
108 | Height: h,
109 | Miner: maddr,
110 | Ticket: nil,
111 | },
112 | parentEpoch: ph,
113 | t: time.Now(),
114 | state: types.Mining,
115 | },
116 | {
117 | bh: &venusTypes.BlockHeader{
118 | Parents: parents,
119 | Height: h,
120 | Miner: maddr,
121 | Ticket: &venusTypes.Ticket{
122 | VRFProof: []byte("====1====="),
123 | },
124 | ParentStateRoot: mockCid,
125 | ParentMessageReceipts: mockCid,
126 | Messages: mockCid,
127 | },
128 | parentEpoch: ph,
129 | t: time.Time{},
130 | state: types.Success,
131 | },
132 | }
133 |
134 | ctx := context.Background()
135 |
136 | // stm: @VENUSMINER_NODE_MODULES_SLASHFILTER_MYSQL_HAS_BLOCK_001, @VENUSMINER_NODE_MODULES_AUTH_MANAGER_PUT_BLOCK_001
137 | t.Run("new block", func(t *testing.T) {
138 | cs := cases[0]
139 | checkHasBlock(t, sf, cs.bh, false)
140 | require.NoError(t, sf.PutBlock(ctx, cs.bh, cs.parentEpoch, cs.t, cs.state))
141 | // BlockHeader in case[0], don't have a ticket, `HasBlock` should return false.
142 | checkHasBlock(t, sf, cs.bh, false)
143 | })
144 |
145 | t.Run("update block", func(t *testing.T) {
146 | cs := cases[1]
147 | err := sf.PutBlock(ctx, cs.bh, cs.parentEpoch, cs.t, cs.state)
148 | require.NoError(t, err)
149 | // After updated, block.Ticket is not nil, and its len(Cids) won't be zero.
150 | // hasBlock should be 'true'
151 | checkHasBlock(t, sf, cs.bh, true)
152 | })
153 | }
154 |
155 | func testMinedBlock(t *testing.T, sf SlashFilterAPI, clear func() error) {
156 | const mdata = "f021344"
157 | maddr, err := address.NewFromString(mdata)
158 | require.NoErrorf(t, err, "parse miner address %s", mdata)
159 |
160 | var parents []cid.Cid
161 | testutil.Provide(t, &parents, testutil.WithSliceLen(2))
162 |
163 | mockCid, _ := cid.Parse("bafkqaaa")
164 | bh := &venusTypes.BlockHeader{
165 | Height: abi.ChainEpoch(100),
166 | Miner: maddr,
167 | Ticket: &venusTypes.Ticket{
168 | VRFProof: []byte("====1====="),
169 | },
170 | Parents: parents,
171 | ParentStateRoot: mockCid,
172 | ParentMessageReceipts: mockCid,
173 | Messages: mockCid,
174 | }
175 | ph := abi.ChainEpoch(99)
176 |
177 | ctx := context.Background()
178 |
179 | t.Run("general", func(t *testing.T) {
180 | require.NoError(t, clear())
181 | err = sf.MinedBlock(ctx, bh, ph)
182 | require.Nil(t, err)
183 | })
184 |
185 | t.Run("time-offset mining", func(t *testing.T) {
186 | require.NoError(t, clear())
187 | require.NoError(t, sf.PutBlock(ctx, bh, ph, time.Now(), types.Success))
188 |
189 | // change bh.Message to simulate case of time-offset mining fault(2 blocks with the same parents)
190 | testutil.Provide(t, &bh.Messages)
191 | err = sf.MinedBlock(ctx, bh, ph)
192 | require.Error(t, err)
193 | require.Greater(t, strings.Index(err.Error(), "time-offset"), 0)
194 | })
195 |
196 | t.Run("parent-grinding", func(t *testing.T) {
197 | require.NoError(t, clear())
198 | err = sf.PutBlock(ctx, bh, ph, time.Now(), types.Success)
199 | require.NoError(t, err)
200 |
201 | blockId := bh.Cid()
202 |
203 | // simulate case of "parent griding" fault
204 | // parents of new block header don't include previous block,
205 | testutil.Provide(t, &bh.Parents, testutil.WithSliceLen(2))
206 |
207 | bh.Height = bh.Height + 1
208 | err = sf.MinedBlock(ctx, bh, ph+1)
209 | require.Error(t, err)
210 | require.Greater(t, strings.Index(err.Error(), "parent-grinding fault"), 0)
211 |
212 | // include parent block into its `parents`, expect success..
213 | bh.Parents = append(bh.Parents, blockId)
214 | err = sf.MinedBlock(ctx, bh, ph+1)
215 | require.NoError(t, err)
216 | })
217 | }
218 |
--------------------------------------------------------------------------------
/node/modules/storage.go:
--------------------------------------------------------------------------------
1 | package modules
2 |
3 | import (
4 | "context"
5 |
6 | types2 "github.com/ipfs-force-community/sophon-miner/types"
7 |
8 | "go.uber.org/fx"
9 |
10 | "github.com/ipfs-force-community/sophon-miner/node/modules/helpers"
11 | "github.com/ipfs-force-community/sophon-miner/node/repo"
12 | )
13 |
14 | func LockedRepo(lr repo.LockedRepo) func(lc fx.Lifecycle) repo.LockedRepo {
15 | return func(lc fx.Lifecycle) repo.LockedRepo {
16 | lc.Append(fx.Hook{
17 | OnStop: func(_ context.Context) error {
18 | return lr.Close()
19 | },
20 | })
21 |
22 | return lr
23 | }
24 | }
25 |
26 | func Datastore(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRepo) (types2.MetadataDS, error) {
27 | ctx := helpers.LifecycleCtx(mctx, lc)
28 | mds, err := r.Datastore(ctx, "/metadata")
29 | if err != nil {
30 | return nil, err
31 | }
32 |
33 | return mds, nil
34 | }
35 |
--------------------------------------------------------------------------------
/node/options.go:
--------------------------------------------------------------------------------
1 | package node
2 |
3 | import (
4 | "reflect"
5 |
6 | "go.uber.org/fx"
7 | )
8 |
9 | // Option is a functional option which can be used with the New function to
10 | // change how the node is constructed
11 | //
12 | // Options are applied in sequence
13 | type Option func(*Settings) error
14 |
15 | // Options groups multiple options into one
16 | func Options(opts ...Option) Option {
17 | return func(s *Settings) error {
18 | for _, opt := range opts {
19 | if err := opt(s); err != nil {
20 | return err
21 | }
22 | }
23 | return nil
24 | }
25 | }
26 |
27 | // Error is a special option which returns an error when applied
28 | func Error(err error) Option {
29 | return func(_ *Settings) error {
30 | return err
31 | }
32 | }
33 |
34 | func ApplyIf(check func(s *Settings) bool, opts ...Option) Option {
35 | return func(s *Settings) error {
36 | if check(s) {
37 | return Options(opts...)(s)
38 | }
39 | return nil
40 | }
41 | }
42 |
43 | func If(b bool, opts ...Option) Option {
44 | return ApplyIf(func(s *Settings) bool {
45 | return b
46 | }, opts...)
47 | }
48 |
49 | // Override option changes constructor for a given type
50 | func Override(typ, constructor interface{}) Option {
51 | return func(s *Settings) error {
52 | if i, ok := typ.(invoke); ok {
53 | s.invokes[i] = fx.Invoke(constructor)
54 | return nil
55 | }
56 |
57 | if c, ok := typ.(special); ok {
58 | s.modules[c] = fx.Provide(constructor)
59 | return nil
60 | }
61 | ctor := as(constructor, typ)
62 | rt := reflect.TypeOf(typ).Elem()
63 |
64 | s.modules[rt] = fx.Provide(ctor)
65 | return nil
66 | }
67 | }
68 |
69 | func Unset(typ interface{}) Option {
70 | return func(s *Settings) error {
71 | if i, ok := typ.(invoke); ok {
72 | s.invokes[i] = nil
73 | return nil
74 | }
75 |
76 | if c, ok := typ.(special); ok {
77 | delete(s.modules, c)
78 | return nil
79 | }
80 | rt := reflect.TypeOf(typ).Elem()
81 |
82 | delete(s.modules, rt)
83 | return nil
84 | }
85 | }
86 |
87 | // From(*T) -> func(t T) T {return t}
88 | func From(typ interface{}) interface{} {
89 | rt := []reflect.Type{reflect.TypeOf(typ).Elem()}
90 | ft := reflect.FuncOf(rt, rt, false)
91 | return reflect.MakeFunc(ft, func(args []reflect.Value) (results []reflect.Value) {
92 | return args
93 | }).Interface()
94 | }
95 |
96 | // from go-ipfs
97 | // as casts input constructor to a given interface (if a value is given, it
98 | // wraps it into a constructor).
99 | //
100 | // Note: this method may look like a hack, and in fact it is one.
101 | // This is here only because https://github.com/uber-go/fx/issues/673 wasn't
102 | // released yet
103 | //
104 | // Note 2: when making changes here, make sure this method stays at
105 | // 100% coverage. This makes it less likely it will be terribly broken
106 | func as(in interface{}, as interface{}) interface{} {
107 | outType := reflect.TypeOf(as)
108 |
109 | if outType.Kind() != reflect.Ptr {
110 | panic("outType is not a pointer")
111 | }
112 |
113 | if reflect.TypeOf(in).Kind() != reflect.Func {
114 | ctype := reflect.FuncOf(nil, []reflect.Type{outType.Elem()}, false)
115 |
116 | return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) {
117 | out := reflect.New(outType.Elem())
118 | out.Elem().Set(reflect.ValueOf(in))
119 |
120 | return []reflect.Value{out.Elem()}
121 | }).Interface()
122 | }
123 |
124 | inType := reflect.TypeOf(in)
125 |
126 | ins := make([]reflect.Type, inType.NumIn())
127 | outs := make([]reflect.Type, inType.NumOut())
128 |
129 | for i := range ins {
130 | ins[i] = inType.In(i)
131 | }
132 | outs[0] = outType.Elem()
133 | for i := range outs[1:] {
134 | outs[i+1] = inType.Out(i + 1)
135 | }
136 |
137 | ctype := reflect.FuncOf(ins, outs, false)
138 |
139 | return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) {
140 | outs := reflect.ValueOf(in).Call(args)
141 |
142 | out := reflect.New(outType.Elem())
143 | if outs[0].Type().AssignableTo(outType.Elem()) {
144 | // Out: Iface = In: *Struct; Out: Iface = In: OtherIface
145 | out.Elem().Set(outs[0])
146 | } else {
147 | // Out: Iface = &(In: Struct)
148 | t := reflect.New(outs[0].Type())
149 | t.Elem().Set(outs[0])
150 | out.Elem().Set(t)
151 | }
152 | outs[0] = out.Elem()
153 |
154 | return outs
155 | }).Interface()
156 | }
157 |
--------------------------------------------------------------------------------
/node/repo/fsrepo_ds.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 |
9 | ldbopts "github.com/syndtr/goleveldb/leveldb/opt"
10 |
11 | "github.com/ipfs/go-datastore"
12 | levelds "github.com/ipfs/go-ds-leveldb"
13 | measure "github.com/ipfs/go-ds-measure"
14 | )
15 |
16 | type dsCtor func(path string, readonly bool) (datastore.Batching, error)
17 |
18 | var fsDatastores = map[string]dsCtor{
19 | "metadata": levelDs,
20 | }
21 |
22 | func levelDs(path string, readonly bool) (datastore.Batching, error) {
23 | return levelds.NewDatastore(path, &levelds.Options{
24 | Compression: ldbopts.NoCompression,
25 | NoSync: false,
26 | Strict: ldbopts.StrictAll,
27 | ReadOnly: readonly,
28 | })
29 | }
30 |
31 | func (fsr *fsLockedRepo) openDatastores(readonly bool) (map[string]datastore.Batching, error) {
32 | if err := os.MkdirAll(fsr.join(fsDatastore), 0755); err != nil {
33 | return nil, fmt.Errorf("mkdir %s: %w", fsr.join(fsDatastore), err)
34 | }
35 |
36 | out := map[string]datastore.Batching{}
37 |
38 | for p, ctor := range fsDatastores {
39 | prefix := datastore.NewKey(p)
40 |
41 | // TODO: optimization: don't init datastores we don't need
42 | ds, err := ctor(fsr.join(filepath.Join(fsDatastore, p)), readonly)
43 | if err != nil {
44 | return nil, fmt.Errorf("opening datastore %s: %w", prefix, err)
45 | }
46 |
47 | ds = measure.New("fsrepo."+p, ds)
48 |
49 | out[datastore.NewKey(p).String()] = ds
50 | }
51 |
52 | return out, nil
53 | }
54 |
55 | func (fsr *fsLockedRepo) Datastore(_ context.Context, ns string) (datastore.Batching, error) {
56 | fsr.dsOnce.Do(func() {
57 | fsr.ds, fsr.dsErr = fsr.openDatastores(fsr.readonly)
58 | })
59 |
60 | if fsr.dsErr != nil {
61 | return nil, fsr.dsErr
62 | }
63 | ds, ok := fsr.ds[ns]
64 | if ok {
65 | return ds, nil
66 | }
67 | return nil, fmt.Errorf("no such datastore: %s", ns)
68 | }
69 |
--------------------------------------------------------------------------------
/node/repo/fsrepo_test.go:
--------------------------------------------------------------------------------
1 | // stm: #unit
2 | package repo
3 |
4 | import (
5 | "os"
6 | "testing"
7 |
8 | "github.com/ipfs-force-community/sophon-miner/node/config"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func genFsRepo(t *testing.T) (*FsRepo, func()) {
13 | path, err := os.MkdirTemp("", "venus-repo-")
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 |
18 | repo, err := NewFS(path)
19 | if err != nil {
20 | t.Fatal(err)
21 | }
22 |
23 | err = repo.Init()
24 | if err != ErrRepoExists && err != nil {
25 | t.Fatal(err)
26 | }
27 | return repo, func() {
28 | _ = os.RemoveAll(path)
29 | }
30 | }
31 |
32 | func TestFsBasic(t *testing.T) {
33 | // stm: @VENUSMINER_NODE_REPO_FSREPO_INIT_001
34 | repo, closer := genFsRepo(t)
35 |
36 | // stm: @VENUSMINER_NODE_REPO_FSREPO_EXISTS_001
37 | exists, err := repo.Exists()
38 | assert.NoError(t, err)
39 | assert.True(t, exists)
40 |
41 | cfg := config.DefaultMinerConfig()
42 | // stm: @VENUSMINER_NODE_REPO_FSREPO_UPDATE_001
43 | err = repo.Update(cfg)
44 | assert.NoError(t, err)
45 |
46 | defer closer()
47 | // stm: @VENUSMINER_NODE_REPO_FSREPO_API_ENDPOINT_001, @VENUSMINER_NODE_REPO_FSREPO_API_TOKEN_001, @VENUSMINER_NODE_REPO_FSREPO_API_CONFIG_001, @VENUSMINER_NODE_REPO_FSREPO_LOCK_001, @VENUSMINER_NODE_REPO_FSREPO_PATH_001, @VENUSMINER_NODE_REPO_FSREPO_CLOSE_001, @VENUSMINER_NODE_REPO_FSREPO_SET_CONFIG_001, @VENUSMINER_NODE_REPO_FSREPO_SET_API_ENDPOINT_001, @VENUSMINER_NODE_REPO_FSREPO_SET_API_TOKEN_001, @VENUSMINER_NODE_REPO_FSREPO_LIST_001, @VENUSMINER_NODE_REPO_FSREPO_GET_001, @VENUSMINER_NODE_REPO_FSREPO_PUT_001, @VENUSMINER_NODE_REPO_FSREPO_DELETE_001
48 | basicTest(t, repo)
49 | }
50 |
--------------------------------------------------------------------------------
/node/repo/interface.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/ipfs/go-datastore"
8 | "github.com/multiformats/go-multiaddr"
9 | )
10 |
11 | var (
12 | ErrNoAPIEndpoint = errors.New("API not running (no endpoint)")
13 | ErrRepoAlreadyLocked = errors.New("repo is already locked")
14 | ErrClosedRepo = errors.New("repo is no longer open")
15 | )
16 |
17 | type Repo interface {
18 | // APIEndpoint returns multiaddress for communication with Lotus API
19 | APIEndpoint() (multiaddr.Multiaddr, error)
20 |
21 | // APIToken returns JWT API Token for use in operations that require auth
22 | APIToken() ([]byte, error)
23 |
24 | // Lock locks the repo for exclusive use.
25 | Lock() (LockedRepo, error)
26 | }
27 |
28 | type LockedRepo interface {
29 | // Close closes repo and removes lock.
30 | Close() error
31 |
32 | // Returns datastore defined in this repo.
33 | // The supplied context must only be used to initialize the datastore.
34 | // The implementation should not retain the context for usage throughout
35 | // the lifecycle.
36 | Datastore(ctx context.Context, namespace string) (datastore.Batching, error)
37 |
38 | // Returns config in this repo
39 | Config() (interface{}, error)
40 | SetConfig(func(interface{})) error
41 |
42 | // SetAPIEndpoint sets the endpoint of the current API
43 | // so it can be read by API clients
44 | SetAPIEndpoint(multiaddr.Multiaddr) error
45 |
46 | // SetAPIToken sets JWT API Token for CLI
47 | SetAPIToken([]byte) error
48 |
49 | // Path returns absolute path of the repo
50 | Path() string
51 |
52 | // SetVersion sets the version number
53 | SetVersion(version string) error
54 |
55 | // Migrate used to upgrade the repo
56 | Migrate() error
57 | }
58 |
--------------------------------------------------------------------------------
/node/repo/migrate.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | "path/filepath"
8 | "strconv"
9 |
10 | "github.com/ipfs-force-community/sophon-miner/build"
11 | "github.com/ipfs-force-community/sophon-miner/node/config"
12 | "github.com/ipfs-force-community/sophon-miner/node/config/migrate"
13 | )
14 |
15 | type UpgradeFunc func(*fsLockedRepo) error
16 |
17 | type versionInfo struct {
18 | version int
19 | upgrade UpgradeFunc
20 | }
21 |
22 | var versionMap = []versionInfo{
23 | {version: 180, upgrade: Version180Upgrade},
24 | }
25 |
26 | func Version180Upgrade(fsr *fsLockedRepo) error {
27 | cfgV, err := config.FromFile(fsr.configPath, &migrate.MinerConfig{})
28 | if err != nil {
29 | return err
30 | }
31 | cfgV170, ok := cfgV.(*migrate.MinerConfig)
32 |
33 | if ok {
34 | // upgrade
35 | if err := fsr.SetConfig(func(i interface{}) {
36 | cfg := i.(*config.MinerConfig)
37 | cfgV170.ToMinerConfig(cfg)
38 | }); err != nil {
39 | return fmt.Errorf("modify config failed: %w", err)
40 | }
41 | }
42 |
43 | return nil
44 | }
45 |
46 | func (fsr *fsLockedRepo) Migrate() error {
47 | filePath := filepath.Join(fsr.path, fsVersion)
48 | f, err := os.Open(filePath)
49 | if err != nil && !os.IsNotExist(err) {
50 | return err
51 | }
52 |
53 | actVerStr := "0"
54 | if err == nil {
55 | defer f.Close() //nolint: errcheck
56 |
57 | data, err := io.ReadAll(f)
58 | if err != nil {
59 | return fmt.Errorf("failed to read %s: %w", filePath, err)
60 | }
61 | actVerStr = string(data)
62 | }
63 |
64 | actVer, _ := strconv.Atoi(actVerStr)
65 | for _, up := range versionMap {
66 | if up.version > actVer {
67 | if up.upgrade == nil {
68 | log.Infof("version %d no executable function", up.version)
69 | } else {
70 | err = up.upgrade(fsr)
71 | if err != nil {
72 | return fmt.Errorf("upgrade version to %d: %w", up.version, err)
73 | }
74 | log.Infof("success to upgrade version %d to %d", actVer, up.version)
75 | }
76 | actVer = up.version
77 | }
78 | }
79 |
80 | err = fsr.SetVersion(build.Version)
81 | if err != nil {
82 | return fmt.Errorf("modify version failed: %w", err)
83 | }
84 |
85 | return nil
86 | }
87 |
--------------------------------------------------------------------------------
/node/repo/repo_test.go:
--------------------------------------------------------------------------------
1 | // stm: #uint
2 | package repo
3 |
4 | import (
5 | "testing"
6 |
7 | "github.com/multiformats/go-multiaddr"
8 | "github.com/stretchr/testify/assert"
9 |
10 | "github.com/ipfs-force-community/sophon-miner/node/config"
11 | )
12 |
13 | func basicTest(t *testing.T, repo Repo) {
14 | apima, err := repo.APIEndpoint()
15 | if assert.Error(t, err) {
16 | assert.Equal(t, ErrNoAPIEndpoint, err)
17 | }
18 | assert.Nil(t, apima, "with no api endpoint, return should be nil")
19 |
20 | lrepo, err := repo.Lock()
21 | assert.NoError(t, err, "should be able to lock once")
22 | assert.NotNil(t, lrepo, "locked repo shouldn't be nil")
23 |
24 | {
25 | lrepo2, err := repo.Lock()
26 | if assert.Error(t, err) {
27 | assert.Equal(t, ErrRepoAlreadyLocked, err)
28 | }
29 | assert.Nil(t, lrepo2, "with locked repo errors, nil should be returned")
30 | }
31 |
32 | err = lrepo.Close()
33 | assert.NoError(t, err, "should be able to unlock")
34 |
35 | lrepo, err = repo.Lock()
36 | assert.NoError(t, err, "should be able to relock")
37 | assert.NotNil(t, lrepo, "locked repo shouldn't be nil")
38 |
39 | ma, err := multiaddr.NewMultiaddr("/ip4/127.0.0.1/tcp/43244")
40 | assert.NoError(t, err, "creating multiaddr shouldn't error")
41 |
42 | err = lrepo.SetAPIEndpoint(ma)
43 | assert.NoError(t, err, "setting multiaddr shouldn't error")
44 |
45 | apima, err = repo.APIEndpoint()
46 | assert.NoError(t, err, "setting multiaddr shouldn't error")
47 | assert.Equal(t, ma, apima, "returned API multiaddr should be the same")
48 |
49 | err = lrepo.SetAPIToken([]byte("api token"))
50 | assert.NoError(t, err)
51 |
52 | token, err := repo.APIToken()
53 | assert.NoError(t, err)
54 | assert.Equal(t, token, []byte("api token"))
55 |
56 | c1, err := lrepo.Config()
57 | assert.Equal(t, config.DefaultMinerConfig(), c1, "there should be a default config")
58 | assert.NoError(t, err, "config should not error")
59 |
60 | err = lrepo.Close()
61 | assert.NoError(t, err, "should be able to close")
62 |
63 | apima, err = repo.APIEndpoint()
64 | assert.NoError(t, err, "getting multiaddr shouldn't error")
65 | assert.Equal(t, ma, apima, "returned API multiaddr should be the same")
66 |
67 | lrepo, err = repo.Lock()
68 | assert.NoError(t, err, "should be able to relock")
69 | assert.NotNil(t, lrepo, "locked repo shouldn't be nil")
70 | }
71 |
--------------------------------------------------------------------------------
/scripts/build-bundle.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -ex
3 |
4 | ARCHS=(
5 | "darwin"
6 | "linux"
7 | )
8 |
9 | REQUIRED=(
10 | "ipfs"
11 | "sha512sum"
12 | )
13 | for REQUIRE in "${REQUIRED[@]}"
14 | do
15 | command -v "${REQUIRE}" >/dev/null 2>&1 || echo >&2 "'${REQUIRE}' must be installed"
16 | done
17 |
18 | mkdir bundle
19 | pushd bundle
20 |
21 | BINARIES=(
22 | "sophon-miner"
23 | )
24 |
25 | # start ipfs
26 | export IPFS_PATH=`mktemp -d`
27 | ipfs init
28 | ipfs daemon &
29 | PID="$!"
30 | trap "kill -9 ${PID}" EXIT
31 | sleep 30
32 |
33 | for ARCH in "${ARCHS[@]}"
34 | do
35 | mkdir -p "${ARCH}/sophon-miner"
36 | pushd "${ARCH}"
37 | for BINARY in "${BINARIES[@]}"
38 | do
39 | cp "../../${ARCH}/${BINARY}" "sophon-miner/"
40 | chmod +x "sophon-miner/${BINARY}"
41 | done
42 |
43 | tar -zcvf "../sophon-miner_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz" sophon-miner
44 | popd
45 | rm -rf "${ARCH}"
46 |
47 | sha512sum "sophon-miner_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz" > "sophon-miner_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz.sha512"
48 |
49 | ipfs add -q "sophon-miner_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz" > "sophon-miner_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz.cid"
50 | done
51 | popd
52 |
--------------------------------------------------------------------------------
/scripts/publish-release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | pushd bundle
5 |
6 | # make sure we have a token set, api requests won't work otherwise
7 | if [ -z "${GITHUB_TOKEN}" ]; then
8 | echo "\${GITHUB_TOKEN} not set, publish failed"
9 | exit 1
10 | fi
11 |
12 | REQUIRED=(
13 | "jq"
14 | "curl"
15 | )
16 | for REQUIRE in "${REQUIRED[@]}"
17 | do
18 | command -v "${REQUIRE}" >/dev/null 2>&1 || echo >&2 "'${REQUIRE}' must be installed"
19 | done
20 |
21 | #see if the release already exists by tag
22 | RELEASE_RESPONSE=`
23 | curl \
24 | --header "Authorization: token ${GITHUB_TOKEN}" \
25 | "https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/releases/tags/${CIRCLE_TAG}"
26 | `
27 | RELEASE_ID=`echo "${RELEASE_RESPONSE}" | jq '.id'`
28 |
29 | if [ "${RELEASE_ID}" = "null" ]; then
30 | echo "creating release"
31 |
32 | RELEASE_DATA="{
33 | \"tag_name\": \"${CIRCLE_TAG}\",
34 | \"target_commitish\": \"${CIRCLE_SHA1}\",
35 | \"name\": \"${CIRCLE_TAG}\",
36 | \"body\": \"\",
37 | \"prerelease\": false
38 | }"
39 |
40 | # create it if it doesn't exist yet
41 | RELEASE_RESPONSE=`
42 | curl \
43 | --request POST \
44 | --header "Authorization: token ${GITHUB_TOKEN}" \
45 | --header "Content-Type: application/json" \
46 | --data "${RELEASE_DATA}" \
47 | "https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/${CIRCLE_PROJECT_REPONAME}/releases"
48 | `
49 | else
50 | echo "release already exists"
51 | fi
52 |
53 | RELEASE_UPLOAD_URL=`echo "${RELEASE_RESPONSE}" | jq -r '.upload_url' | cut -d'{' -f1`
54 |
55 | for RELEASE_FILE in *.{cid,sha512}
56 | do
57 | echo "Uploading release bundle: ${RELEASE_FILE}"
58 | curl \
59 | --fail \
60 | --request POST \
61 | --header "Authorization: token ${GITHUB_TOKEN}" \
62 | --header "Content-Type: application/octet-stream" \
63 | --data-binary "@${RELEASE_FILE}" \
64 | "$RELEASE_UPLOAD_URL?name=$(basename "${RELEASE_FILE}")"
65 |
66 | echo "Release bundle uploaded: ${RELEASE_FILE}"
67 | done
68 |
69 | popd
70 |
71 | miscellaneous=(
72 | "README.md"
73 | "LICENSE-MIT"
74 | "LICENSE-APACHE"
75 | )
76 | for MISC in "${miscellaneous[@]}"
77 | do
78 | echo "Uploading release bundle: ${MISC}"
79 | curl \
80 | --request POST \
81 | --header "Authorization: token ${GITHUB_TOKEN}" \
82 | --header "Content-Type: application/octet-stream" \
83 | --data-binary "@${MISC}" \
84 | "$RELEASE_UPLOAD_URL?name=$(basename "${MISC}")"
85 |
86 | echo "Release bundle uploaded: ${MISC}"
87 | done
88 |
--------------------------------------------------------------------------------
/types/api.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "github.com/multiformats/go-multiaddr"
5 | )
6 |
7 | type APIEndpoint multiaddr.Multiaddr
8 |
--------------------------------------------------------------------------------
/types/logs.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "github.com/ipfs/go-cid"
5 | "go.uber.org/zap/zapcore"
6 | )
7 |
8 | type LogCids []cid.Cid
9 |
10 | var _ zapcore.ArrayMarshaler = (*LogCids)(nil)
11 |
12 | func (cids LogCids) MarshalLogArray(ae zapcore.ArrayEncoder) error {
13 | for _, c := range cids {
14 | ae.AppendString(c.String())
15 | }
16 | return nil
17 | }
18 |
--------------------------------------------------------------------------------
/types/miner.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/filecoin-project/go-address"
8 | "github.com/filecoin-project/go-state-types/abi"
9 | )
10 |
11 | type NetworkName string
12 |
13 | type MinerInfo struct {
14 | Addr address.Address
15 | Id string
16 | Name string
17 | OpenMining bool
18 | }
19 |
20 | type MinerState struct {
21 | Addr address.Address
22 | IsMining bool
23 | Err []string
24 | }
25 |
26 | type SimpleWinInfo struct {
27 | Epoch abi.ChainEpoch `json:"epoch"`
28 | WinCount int64 `json:"winCount"`
29 | Msg string `json:"msg"`
30 | }
31 |
32 | type CountWinners struct {
33 | Miner address.Address `json:"miner"`
34 | TotalWinCount int64 `json:"totalWinCount"`
35 | Msg string `json:"msg"`
36 | WinEpochList []SimpleWinInfo `json:"winEpochList"`
37 | }
38 |
39 | type StateMining int
40 |
41 | const (
42 | Mining StateMining = iota
43 | Success
44 | Timeout
45 | ChainForked
46 | Error
47 | )
48 |
49 | func (sm StateMining) String() string {
50 | switch sm {
51 | case Mining:
52 | return "Mining"
53 | case Success:
54 | return "Success"
55 | case Timeout:
56 | return "TimeOut"
57 | case ChainForked:
58 | return "ChainForked"
59 | case Error:
60 | return "Error"
61 | default:
62 | return "unknown"
63 | }
64 | }
65 |
66 | type MinedBlock struct {
67 | ParentEpoch int64 `gorm:"column:parent_epoch;type:bigint(20);default:0;NOT NULL"`
68 | ParentKey string `gorm:"column:parent_key;type:varchar(2048);default:'';NOT NULL"`
69 |
70 | Epoch int64 `gorm:"column:epoch;type:bigint(20);NOT NULL;primary_key"`
71 | Miner string `gorm:"column:miner;type:varchar(256);NOT NULL;primary_key"`
72 | Cid string `gorm:"column:cid;type:varchar(256);default:''"`
73 |
74 | WinningAt time.Time `gorm:"column:winning_at;type:datetime"`
75 | MineState StateMining `gorm:"column:mine_state;type:tinyint(4);default:0;comment:0-mining,1-success,2-timeout,3-chain forked,4-error;NOT NULL"`
76 | Consuming int64 `gorm:"column:consuming;type:bigint(10);default:0;NOT NULL"` // reserved
77 | }
78 |
79 | func (m *MinedBlock) TableName() string {
80 | return "miner_blocks"
81 | }
82 |
83 | type BlocksQueryParams struct {
84 | Miners []address.Address
85 | Limit int
86 | Offset int
87 | }
88 |
89 | type QueryRecordParams struct {
90 | Miner address.Address
91 | Epoch abi.ChainEpoch
92 | Limit uint
93 | }
94 |
95 | type ErrorCode int
96 |
97 | const (
98 | ConnectGatewayError ErrorCode = iota
99 | CallNodeRPCError
100 | WalletSignError
101 | )
102 |
103 | func (e ErrorCode) String() string {
104 | switch e {
105 | case ConnectGatewayError:
106 | return "ConnectGatewayError"
107 | case CallNodeRPCError:
108 | return "CallNodeRPCError"
109 | case WalletSignError:
110 | return "WalletSignError"
111 | default:
112 | return "unknown"
113 | }
114 | }
115 |
116 | func (e ErrorCode) Error() string {
117 | return fmt.Sprintf("%d", e)
118 | }
119 |
--------------------------------------------------------------------------------
/types/shutdown.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // ShutdownChan is a channel to which you send a value if you intend to shut
4 | // down the daemon (or miner), including the node and RPC server.
5 | type ShutdownChan chan struct{}
6 |
--------------------------------------------------------------------------------
/types/storage.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "github.com/ipfs/go-datastore"
5 | )
6 |
7 | // MetadataDS stores metadata
8 | // dy default it's namespaced under /metadata in main repo datastore
9 | type MetadataDS datastore.Batching
10 |
--------------------------------------------------------------------------------