From cff6a1a9972fdd243c111fa9f99e0e93e95519e8 Mon Sep 17 00:00:00 2001 From: bigsea <601276804@qq.com> Date: Thu, 30 Jul 2026 21:27:35 +0800 Subject: [PATCH 1/7] chore: bump chain33 dependency to aa71469 Update only the github.com/33cn/chain33 module version to pick up the account blacklist APIs; leave all other dependency versions unchanged. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a9ed59d0d..90a720ebf 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ replace ( ) require ( - github.com/33cn/chain33 v1.69.1-0.20260508025622-0fa35083839d + github.com/33cn/chain33 v1.69.1-0.20260730124452-aa71469c0974 github.com/BurntSushi/toml v1.2.1 github.com/NebulousLabs/Sia v1.3.7 github.com/bitly/go-simplejson v0.5.0 diff --git a/go.sum b/go.sum index 8dacf1a84..f4fab753d 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/33cn/chain33 v1.69.1-0.20260508025622-0fa35083839d h1:4RcD7vxrCuO/1yJ9hg9Anh0cWseMOXGQCpOylGdAH5M= -github.com/33cn/chain33 v1.69.1-0.20260508025622-0fa35083839d/go.mod h1:odrlSSvyZE9Zrrzf4uHk/hLUfzHZlb/8YlhetQaSIps= +github.com/33cn/chain33 v1.69.1-0.20260730124452-aa71469c0974 h1:ixjA9gkE9cyBW5chtUKhBbRLV+HyDShX7Uno30VA+5A= +github.com/33cn/chain33 v1.69.1-0.20260730124452-aa71469c0974/go.mod h1:QCdEczXYJI43WTeFuzEpEhNZ/SfEnLOdFaw6r6LvPz8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= From 07369d3e18cc66bbb59509fcda2fd6120c21d560 Mon Sep 17 00:00:00 2001 From: bigsea <601276804@qq.com> Date: Wed, 12 Aug 2026 13:03:50 +0800 Subject: [PATCH 2/7] feat(evm): select account blacklist by block height MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 配合 chain33 侧黑名单迁移到 mver 多版本配置,EVM 三处调用点从包级 types.IsBlockedAccount(addr) 改为 cfg.IsBlockedAccount(addr, height), 并删掉各自的 IsFork 门控 —— 名单版本已按高度选定,分叉高度之前取到的就是空名单, 再判一次 fork 是冗余的。命中处补上高度与地址日志,便于定位。 注意 chain33 已删除包级 types.IsBlockedAccount,两个仓库必须配套发版。 Co-authored-by: Cursor --- plugin/dapp/evm/executor/exec.go | 9 +++++---- .../vm/runtime/account_blacklist_test.go | 18 ++++++++++++++---- plugin/dapp/evm/executor/vm/runtime/evm.go | 10 ++++++---- plugin/dapp/evm/executor/vm/state/statedb.go | 9 +++++---- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/plugin/dapp/evm/executor/exec.go b/plugin/dapp/evm/executor/exec.go index fa9d25081..23c51a877 100644 --- a/plugin/dapp/evm/executor/exec.go +++ b/plugin/dapp/evm/executor/exec.go @@ -439,18 +439,19 @@ func getCaller(tx *types.Transaction) common.Address { } // checkEvmBlockedAccount 在 EVM 执行内部拦截命中黑名单的地址(发送方/接收方/合约地址)。 -// 与 types.CheckTxBlockedAccount 共用同一黑名单,但按地址维度检查(此时地址已解析为字符串)。 -// 这里走 fork 门控:仅在 ForkAccountBlacklist 高度后生效。 +// 与 types.CheckTxBlockedAccount 共用同一份按高度分版本的名单,但按地址维度检查(此时地址已解析为字符串)。 +// 不需要额外的 fork 门控:名单版本已按高度选定,分叉高度之前取到的是空名单。 // 返回 error 后由调用方按 ExecPack 语义处理(保持 revert + 扣费),不升级为 ExecErr。 func checkEvmBlockedAccount(cfg *types.Chain33Config, height int64, addrs ...string) error { - if cfg == nil || !cfg.IsFork(height, types.ForkAccountBlacklist) { + if cfg == nil { return nil } for _, addr := range addrs { if addr == "" { continue } - if types.IsBlockedAccount(addr) { + if cfg.IsBlockedAccount(addr, height) { + log.Error("checkEvmBlockedAccount hit", "height", height, "addr", addr) return fmt.Errorf("%w: %s", types.ErrBlockedAccount, addr) } } diff --git a/plugin/dapp/evm/executor/vm/runtime/account_blacklist_test.go b/plugin/dapp/evm/executor/vm/runtime/account_blacklist_test.go index 9424058dd..d7de70ab7 100644 --- a/plugin/dapp/evm/executor/vm/runtime/account_blacklist_test.go +++ b/plugin/dapp/evm/executor/vm/runtime/account_blacklist_test.go @@ -21,8 +21,8 @@ const blockedRuntimeAddr = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0" func newBlockedEVM(t *testing.T, blockedAddrs []string) *EVM { t.Helper() cfg := types.NewChain33Config(types.GetDefaultCfgstring()) - // local 标题下 SetAllFork(0),ForkAccountBlacklist 从高度 0 启用 - restore := types.SetBlockedAccountsForTest(blockedAddrs) + // 名单自高度 0 起生效,覆盖下面 Context 的区块高度 1 + restore := cfg.SetBlockedAccountsForTest(0, blockedAddrs) t.Cleanup(restore) ctx := Context{BlockNumber: big.NewInt(1)} return NewEVM(ctx, &state.MemoryStateDB{}, Config{}, cfg) @@ -53,11 +53,21 @@ func TestCheckBlockedAccount(t *testing.T) { t.Run("empty blocklist pass", func(t *testing.T) { cfg := types.NewChain33Config(types.GetDefaultCfgstring()) - restore := types.SetBlockedAccountsForTest([]string{}) - defer restore() + defer cfg.SetBlockedAccountsForTest(0, []string{})() evm := NewEVM(Context{BlockNumber: big.NewInt(1)}, &state.MemoryStateDB{}, Config{}, cfg) assert.NoError(t, checkBlockedAccount(evm, blocked, blocked)) }) + + // 名单自高度 H 起生效时,H 之前的区块必须放行,保证历史回放结果不变 + t.Run("before fork height pass", func(t *testing.T) { + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + defer cfg.SetBlockedAccountsForTest(100, []string{blockedRuntimeAddr})() + evm := NewEVM(Context{BlockNumber: big.NewInt(99)}, &state.MemoryStateDB{}, Config{}, cfg) + assert.NoError(t, checkBlockedAccount(evm, blocked, blocked)) + + evm = NewEVM(Context{BlockNumber: big.NewInt(100)}, &state.MemoryStateDB{}, Config{}, cfg) + assert.Error(t, checkBlockedAccount(evm, blocked, blocked)) + }) } // TestCallBlockedAccount 验证 EVM.Call 在黑名单地址下返回 error(触发上层 revert) diff --git a/plugin/dapp/evm/executor/vm/runtime/evm.go b/plugin/dapp/evm/executor/vm/runtime/evm.go index bb17e1c0c..bd869d994 100644 --- a/plugin/dapp/evm/executor/vm/runtime/evm.go +++ b/plugin/dapp/evm/executor/vm/runtime/evm.go @@ -20,16 +20,18 @@ import ( evmtypes "github.com/33cn/plugin/plugin/dapp/evm/types" ) -// checkBlockedAccount 合约内部调用/创建的黑名单拦截(fork 门控)。 +// checkBlockedAccount 合约内部调用/创建的黑名单拦截,按当前区块高度选取名单版本。 // 命中返回包装后的 types.ErrBlockedAccount,由调用方返回 error 触发 RevertToSnapshot。 -// 与 executor 层共用 types 黑名单,按 EVM 地址(0x 字符串)检查。 +// 与 executor 层共用同一份名单,按 EVM 地址(0x 字符串)检查。 func checkBlockedAccount(evm *EVM, addrs ...common.Address) error { cfg := evm.cfg - if cfg == nil || evm.BlockNumber == nil || !cfg.IsFork(evm.BlockNumber.Int64(), types.ForkAccountBlacklist) { + if cfg == nil || evm.BlockNumber == nil { return nil } + height := evm.BlockNumber.Int64() for _, addr := range addrs { - if types.IsBlockedAccount(addr.String()) { + if cfg.IsBlockedAccount(addr.String(), height) { + log.Error("checkBlockedAccount hit", "height", height, "addr", addr.String()) return fmt.Errorf("%w: %s", types.ErrBlockedAccount, addr.String()) } } diff --git a/plugin/dapp/evm/executor/vm/state/statedb.go b/plugin/dapp/evm/executor/vm/state/statedb.go index 6734806a6..3135d3cac 100644 --- a/plugin/dapp/evm/executor/vm/state/statedb.go +++ b/plugin/dapp/evm/executor/vm/state/statedb.go @@ -97,18 +97,19 @@ func NewMemoryStateDB(StateDB db.KV, LocalDB db.KVDB, CoinsAccount *account.DB, return mdb } -// isBlockedAccount 账户黑名单兜底判定,带 ForkAccountBlacklist 门控。 -// statedb 的转账结果直接进入状态计算,未到分叉高度时不得改变执行结果,否则会与未升级节点分链。 +// isBlockedAccount 账户黑名单兜底判定,按当前区块高度选取名单版本。 +// statedb 的转账结果直接进入状态计算,名单版本必须严格跟随高度, +// 否则会用新名单判定旧区块,与未升级节点分链。 func (mdb *MemoryStateDB) isBlockedAccount(addrs ...string) bool { if mdb.api == nil { return false } cfg := mdb.api.GetConfig() - if cfg == nil || !cfg.IsFork(mdb.blockHeight, types.ForkAccountBlacklist) { + if cfg == nil { return false } for _, addr := range addrs { - if types.IsBlockedAccount(addr) { + if cfg.IsBlockedAccount(addr, mdb.blockHeight) { return true } } From 51d44fb73c71a99c3b510010bb9f803c8b59bdd8 Mon Sep 17 00:00:00 2001 From: bigsea <601276804@qq.com> Date: Fri, 11 Sep 2026 14:50:12 +0800 Subject: [PATCH 3/7] build(deps): bump chain33 to v1.71.0 for mver account blacklist Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d66748b0d..5cac7f74f 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.22.12 replace github.com/ava-labs/avalanchego => github.com/33cn/avalanchego v1.10.10-0.20240529041529-ada691598153 require ( - github.com/33cn/chain33 v1.69.1-0.20260806044231-1edb5ac8d4fd + github.com/33cn/chain33 v1.71.0 github.com/BurntSushi/toml v1.3.2 github.com/NebulousLabs/Sia v1.3.7 github.com/bitly/go-simplejson v0.5.0 diff --git a/go.sum b/go.sum index b1b17f029..83fc1639a 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/33cn/chain33 v1.69.1-0.20260806044231-1edb5ac8d4fd h1:1W3IAqtTf9ILrH326JWYcdBFsLxNhsvWSGD6lOyVbZw= -github.com/33cn/chain33 v1.69.1-0.20260806044231-1edb5ac8d4fd/go.mod h1:QCdEczXYJI43WTeFuzEpEhNZ/SfEnLOdFaw6r6LvPz8= +github.com/33cn/chain33 v1.71.0 h1:n3XbgHU0vcCL/wBG3+KlnKX3bzXiZAvn5IC6c4lHQTo= +github.com/33cn/chain33 v1.71.0/go.mod h1:QCdEczXYJI43WTeFuzEpEhNZ/SfEnLOdFaw6r6LvPz8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go v41.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= From 91bbf89122135a50a57b6d3b9d8e874ec963b0a3 Mon Sep 17 00:00:00 2001 From: bigsea <601276804@qq.com> Date: Fri, 11 Sep 2026 14:50:12 +0800 Subject: [PATCH 4/7] feat: add mver blacklist config templates and update EVM docs Empty [mver.blacklist] sections keep ForkAccountBlacklist=-1 safe to start, and document height-based snapshots instead of IsFork gating. Co-authored-by: Cursor --- chain33.para.toml | 12 ++++++++ chain33.toml | 11 +++++++ docs/security/evm-account-blacklist.md | 29 ++++++++++++------- plugin/dapp/evm/cmd/ci/chain33.evm.test.toml | 13 +++++++++ .../dapp/evm/cmd/ci2/chain33.proxyminer.toml | 11 +++++++ 5 files changed, 66 insertions(+), 10 deletions(-) diff --git a/chain33.para.toml b/chain33.para.toml index 9e6d017af..e0e04aef5 100644 --- a/chain33.para.toml +++ b/chain33.para.toml @@ -109,6 +109,18 @@ genesisBlockTime=1514533390 genesis="14KEKbYtKKQm4wMthSK9J4La4nAiidGozt" minerExecs=["paracross"] #配置挖矿合约 +# 账户黑名单:命中地址禁止收发任何交易,支持base58与0x两种地址格式 +# 名单按高度分版本演进,每个 [mver.blacklist.<分叉名>] 段是一份全量名单而非增量, +# 高度 h 生效的是「高度不大于 h 的最大分叉」那一份;下面的基线段在任何分叉生效前使用 +# 启用时须在 [fork.system] 把 ForkAccountBlacklist 配成真实高度,并取消下面 V1 段注释 +[mver.blacklist] +accountBlacklist=[] + +#[mver.blacklist.ForkAccountBlacklist] +#accountBlacklist=["1xxx", "0xyyy"] +#[mver.blacklist.ForkAccountBlacklistV2] +#accountBlacklist=["1xxx", "0xyyy", "0xzzz"] + [mver.consensus] fundKeyAddr = "1BQXS6TxaYYG5mADaWij4AxhZZUTpw95a5" powLimitBits = "0x1f00ffff" diff --git a/chain33.toml b/chain33.toml index c3d96315c..6585bfc56 100644 --- a/chain33.toml +++ b/chain33.toml @@ -155,6 +155,17 @@ genesis="14KEKbYtKKQm4wMthSK9J4La4nAiidGozt" minerExecs=["ticket", "autonomy"] enableBestBlockCmp=true +# 账户黑名单:命中地址禁止收发任何交易,支持base58与0x两种地址格式 +# 名单按高度分版本演进,每个 [mver.blacklist.<分叉名>] 段是一份全量名单而非增量, +# 高度 h 生效的是「高度不大于 h 的最大分叉」那一份;下面的基线段在任何分叉生效前使用 +# 启用时须在 [fork.system] 把 ForkAccountBlacklist 配成真实高度,并取消下面 V1 段注释 +[mver.blacklist] +accountBlacklist=[] + +#[mver.blacklist.ForkAccountBlacklist] +#accountBlacklist=["1xxx", "0xyyy"] +#[mver.blacklist.ForkAccountBlacklistV2] +#accountBlacklist=["1xxx", "0xyyy", "0xzzz"] [mver.consensus] fundKeyAddr = "1BQXS6TxaYYG5mADaWij4AxhZZUTpw95a5" diff --git a/docs/security/evm-account-blacklist.md b/docs/security/evm-account-blacklist.md index 82b1c75b7..462c52666 100644 --- a/docs/security/evm-account-blacklist.md +++ b/docs/security/evm-account-blacklist.md @@ -19,13 +19,16 @@ chain33 在框架层引入 `ForkAccountBlacklist` 与 `types.CheckTxBlockedAccou ## 1. chain33 框架层覆盖了什么 -三处入口最终都走 `types.checkTxBlockedAccountCore`(chain33 `types/account_blacklist.go`): +三处入口最终都走 `types.checkTxBlockedAccount`(chain33 `types/account_blacklist.go`)。 +启动时按 `[mver.blacklist.*]` 与分叉高度打成只读快照,运行期用 `blacklistAt(height)` 取 +「高度不大于 h 的最大版本」,**调用处不再写 `IsFork(height, ForkAccountBlacklist)`**。 +分叉高度之前取到的是空名单,效果等同未启用。 -| 层 | 位置 | fork 门控 | +| 层 | 位置 | 选版高度 | |---|---|---| -| mempool 入口 | `system/mempool/check.go:76`、`eventprocess.go:216/339` | 无,随升级立即生效 | -| 出块 / 验块 | `system/consensus/base.go:606` `AddTxsToBlock` | 有 | -| executor.checkTx | `executor/execenv.go:173` | 有 | +| mempool 入口 | `CheckTxBlockedAccountImmediate`(`mempool/check.go`、延时交易入口) | 即将打包的下一高度 `last+1` | +| 出块 / 验块 | `BaseClient.AddTxsToBlock` | 当前块高度 | +| executor.checkTx | `executor/execenv.go` `checkTx` / `checkTxGroup` | `e.height`(出块/回放=块高度;进池 EventCheckTx 目前仍是 last) | 判定四个维度:`tx.From()`、`tx.GetTo()`、`tx.GetRealToAddr()`、 EVM payload 中的 `ContractAddr` 与**恰好 20 字节**的 `Para`。 @@ -48,7 +51,7 @@ EVM payload 中的 `ContractAddr` 与**恰好 20 字节**的 `Para`。 框架只解析交易**信封**。合约跑起来之后内部 CALL 了谁、以谁的名义付款, 只存在于运行时栈和 calldata 里。以下每条路径在 `blacklist_gap_test.go` 都有对应用例, -用真实 coins 账户断言余额,并以 fork 关闭为对照证明路径确实是活的。 +用真实 coins 账户断言余额,并以名单尚未生效的高度为对照证明路径确实是活的。 ### B1. 黑名单合约被内部 CALL 唤醒后转出自身余额 @@ -151,8 +154,14 @@ chain33 的 `Para` 维度看不见,而 `BytesToAddress` 取后 20 字节仍能 在此之前,"纵深防御"只作为评审结论记录在源码注释与本文中,不在本 PR 内删除。 -## 6. fork 门控 +## 6. 按高度选版(不再单独 IsFork) -statedb / runtime 两层的检查都以 `cfg.IsFork(height, ForkAccountBlacklist)` 门控。 -未到分叉高度时不得改变执行结果,否则与未升级节点分链(用例 `TestGap_ForkGateBlocksNothingBeforeHeight`)。 -mempool 入口无门控是故意的:它是节点本地行为、不进状态计算,随二进制升级立即止血。 +statedb / runtime / `checkEvmBlockedAccount` 都走 `cfg.IsBlockedAccount(addr, height)`, +与框架共用同一份快照。未到对应分叉高度时该版本名单为空,不得改变执行结果, +否则与未升级节点分链(用例 `TestGap_ForkGateBlocksNothingBeforeHeight` 用 +`SetBlockedAccountsForTest(fromHeight, addrs)` 把名单推到未来高度来验证)。 + +不要在 EVM 侧再加一层 `IsFork(height, ForkAccountBlacklist)`:门控已经固化在快照的版本高度里, +重复门控会在名单演进(V2 空名单解除、V3 再拦)时和框架选版不一致。 + +mempool 入口按 `last+1` 选版是为了和下一块打包对齐;真正的网络级拦截点仍是共识在已到达高度上的判定。 diff --git a/plugin/dapp/evm/cmd/ci/chain33.evm.test.toml b/plugin/dapp/evm/cmd/ci/chain33.evm.test.toml index 4f14c3293..828c8376e 100644 --- a/plugin/dapp/evm/cmd/ci/chain33.evm.test.toml +++ b/plugin/dapp/evm/cmd/ci/chain33.evm.test.toml @@ -154,6 +154,19 @@ genesisBlockTime=1514533394 genesis="0xd83b69C56834E85e023B1738E69BFA2F0dd52905" hotkeyAddr="0xd83b69C56834E85e023B1738E69BFA2F0dd52905" waitTxMs=10 + +# 账户黑名单:命中地址禁止收发任何交易,支持base58与0x两种地址格式 +# 名单按高度分版本演进,每个 [mver.blacklist.<分叉名>] 段是一份全量名单而非增量, +# 高度 h 生效的是「高度不大于 h 的最大分叉」那一份;下面的基线段在任何分叉生效前使用 +# 启用时须在 [fork.system] 把 ForkAccountBlacklist 配成真实高度,并取消下面 V1 段注释 +[mver.blacklist] +accountBlacklist=[] + +#[mver.blacklist.ForkAccountBlacklist] +#accountBlacklist=["1xxx", "0xyyy"] +#[mver.blacklist.ForkAccountBlacklistV2] +#accountBlacklist=["1xxx", "0xyyy", "0xzzz"] + [mver.consensus] fundKeyAddr = "1BQXS6TxaYYG5mADaWij4AxhZZUTpw95a5" powLimitBits="0x1f00ffff" diff --git a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml index 618409ca2..3eefa11b9 100644 --- a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml +++ b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml @@ -157,6 +157,17 @@ genesis="0xDe79A84DD3A16BB91044167075dE17a1CA4b1d6b" minerExecs=["ticket", "autonomy"] enableBestBlockCmp=true +# 账户黑名单:命中地址禁止收发任何交易,支持base58与0x两种地址格式 +# 名单按高度分版本演进,每个 [mver.blacklist.<分叉名>] 段是一份全量名单而非增量, +# 高度 h 生效的是「高度不大于 h 的最大分叉」那一份;下面的基线段在任何分叉生效前使用 +# 启用时须在 [fork.system] 把 ForkAccountBlacklist 配成真实高度,并取消下面 V1 段注释 +[mver.blacklist] +accountBlacklist=[] + +#[mver.blacklist.ForkAccountBlacklist] +#accountBlacklist=["1xxx", "0xyyy"] +#[mver.blacklist.ForkAccountBlacklistV2] +#accountBlacklist=["1xxx", "0xyyy", "0xzzz"] [mver.consensus] fundKeyAddr = "1BQXS6TxaYYG5mADaWij4AxhZZUTpw95a5" From 0b901b19e532d8138dc23108880ea1e874fa7061 Mon Sep 17 00:00:00 2001 From: bigsea <601276804@qq.com> Date: Fri, 11 Sep 2026 14:50:12 +0800 Subject: [PATCH 5/7] test(evm): cover blacklist mver V1 block, V2 empty allow, V3 restore Co-authored-by: Cursor --- .../evm/executor/blacklist_fork_node_test.go | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 plugin/dapp/evm/executor/blacklist_fork_node_test.go diff --git a/plugin/dapp/evm/executor/blacklist_fork_node_test.go b/plugin/dapp/evm/executor/blacklist_fork_node_test.go new file mode 100644 index 000000000..793c4583c --- /dev/null +++ b/plugin/dapp/evm/executor/blacklist_fork_node_test.go @@ -0,0 +1,289 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package executor_test + +import ( + "errors" + "math/rand" + "strings" + "testing" + + "github.com/33cn/chain33/common" + "github.com/33cn/chain33/common/address" + "github.com/33cn/chain33/common/crypto" + rpctypes "github.com/33cn/chain33/rpc/types" + _ "github.com/33cn/chain33/system" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + "github.com/33cn/chain33/util/testnode" + _ "github.com/33cn/plugin/plugin/dapp/evm" + "github.com/stretchr/testify/require" +) + +// 本文件走 testnode 完整节点栈,依赖 plugin/go.mod 中的 github.com/33cn/chain33 +// (当前为 v1.71.0,已含 mver 多版本黑名单)。 +// +// 场景:节点按 mver 启动后 +// V1 拦截指定地址 → V2 空名单解除拦截 → V3 把同一地址重新拉黑。 +// coins 与 EVM(ContractAddr 命中)都走 queue SendTx 和 JSON-RPC SendTransaction: +// 拦截时 mempool 拒绝,放行时 WaitTx 确认上链。 +// solo 只在 mempool 有交易时出块,跨高度用 none 交易推进,不能空等。 +// 创世地址 14KEKbYtKKQm4wMthSK9J4La4nAiidGozt 不能进名单,否则出块和 SendHot 会被自己拦住。 + +const ( + forkV1Height = int64(2) + forkV2Height = int64(5) + forkV3Height = int64(15) + + // TestPrivkeyList[2],不是 genesis / hot + blockedBTCAddr = "1EbDHAXpoiewjPLX9uqoz38HsKqMXayZrF" + blockedETHAddr = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0" + + defaultBlacklistSection = "[mver.blacklist]\naccountBlacklist=[]\n" +) + +func TestNodeBlacklistV2ClearThenV3Restore(t *testing.T) { + cfg := newNodeBlacklistCfg(t) + mock := testnode.NewWithConfig(cfg, nil) + defer mock.Close() + mock.Listen() + + require.NoError(t, mock.SendHot(), "genesis 转给 hot 必须在名单之外") + + t.Run("节点启动后配置快照", func(t *testing.T) { + assertCfgSnapshot(t, cfg) + }) + + t.Run("V1 生效后拦截", func(t *testing.T) { + mineTo(t, mock, forkV1Height-1) + assertNextInWindow(t, mock, forkV1Height, forkV2Height) + assertSendBlocked(t, mock, true) + }) + + t.Run("V2 空名单后放行", func(t *testing.T) { + mineTo(t, mock, forkV2Height) + assertNextInWindow(t, mock, forkV2Height, forkV3Height) + assertSendBlocked(t, mock, false) + }) + + t.Run("V3 重新拉黑后拦截", func(t *testing.T) { + mineTo(t, mock, forkV3Height) + require.GreaterOrEqual(t, nextHeight(t, mock), forkV3Height) + assertSendBlocked(t, mock, true) + }) +} + +func newNodeBlacklistCfg(t *testing.T) *types.Chain33Config { + t.Helper() + cfgstring := types.GetDefaultCfgstring() + cfgstring = strings.Replace(cfgstring, `Title="local"`, `Title="chain33"`+"\nDisableForkCheck=true", 1) + cfgstring = strings.Replace(cfgstring, defaultBlacklistSection, "", 1) + cfgstring += nodeBlacklistTOML() + // V2/V3 未在 RegisterSystemFork 里登记,toml 里写出高度后由 initForkConfig 注入。 + // Title 不能是 local,否则 SetAllFork(0) 会把三个版本压到同一高度,只留下 V3。 + return types.NewChain33Config(cfgstring) +} + +func nodeBlacklistTOML() string { + return ` +[mver.blacklist] +accountBlacklist=[] +[mver.blacklist.ForkAccountBlacklist] +accountBlacklist=["` + blockedBTCAddr + `","` + blockedETHAddr + `"] +[mver.blacklist.ForkAccountBlacklistV2] +accountBlacklist=[] +[mver.blacklist.ForkAccountBlacklistV3] +accountBlacklist=["` + blockedBTCAddr + `","` + blockedETHAddr + `"] + +[fork.system] +ForkAccountBlacklist=2 +ForkAccountBlacklistV2=5 +ForkAccountBlacklistV3=15 + +[fork.sub.coins] +Enable=0 +ForkFriendExecer=0 + +[fork.sub.evm] +Enable=0 +` +} + +func assertCfgSnapshot(t *testing.T, cfg *types.Chain33Config) { + t.Helper() + hot := address.PubKeyToAddr(address.DefaultID, util.TestPrivkeyList[0].PubKey().Bytes()) + priv := util.TestPrivkeyList[0] + + cases := []struct { + name string + height int64 + blocked bool + }{ + {"V1 前放行", forkV1Height - 1, false}, + {"V1 拦截", forkV1Height, true}, + {"V2 前仍拦截", forkV2Height - 1, true}, + {"V2 空名单放行", forkV2Height, false}, + {"V3 前仍放行", forkV3Height - 1, false}, + {"V3 重新拦截", forkV3Height, true}, + } + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + require.Equal(t, c.blocked, cfg.IsBlockedAccount(blockedBTCAddr, c.height), "btc height=%d", c.height) + require.Equal(t, c.blocked, cfg.IsBlockedAccount(blockedETHAddr, c.height), "eth height=%d", c.height) + require.False(t, cfg.IsBlockedAccount(hot, c.height), "hot 不得进名单") + + coinsErr := types.CheckTxBlockedAccount(cfg, c.height, coinsTx(cfg, priv, blockedBTCAddr)) + evmErr := types.CheckTxBlockedAccount(cfg, c.height, evmTx(cfg, priv, blockedETHAddr)) + if c.blocked { + require.Error(t, coinsErr) + require.Error(t, evmErr) + require.True(t, errors.Is(coinsErr, types.ErrBlockedAccount)) + require.True(t, errors.Is(evmErr, types.ErrBlockedAccount)) + return + } + require.NoError(t, coinsErr) + require.NoError(t, evmErr) + }) + } +} + +func assertSendBlocked(t *testing.T, mock *testnode.Chain33Mock, wantBlocked bool) { + t.Helper() + next := nextHeight(t, mock) + var onChain []namedHash + t.Run("queue", func(t *testing.T) { + onChain = append(onChain, assertSendVia(t, mock, next, wantBlocked, "queue", sendTx)...) + }) + t.Run("jsonrpc", func(t *testing.T) { + onChain = append(onChain, assertSendVia(t, mock, next, wantBlocked, "jsonrpc", sendTxRPC)...) + }) + if wantBlocked { + return + } + for _, item := range onChain { + waitOnChain(t, mock, item.hash, item.kind) + } +} + +type namedHash struct { + kind string + hash []byte +} + +func assertSendVia(t *testing.T, mock *testnode.Chain33Mock, next int64, wantBlocked bool, via string, send sendTxFn) []namedHash { + t.Helper() + cfg := mock.GetClient().GetConfig() + hot := mock.GetHotKey() + + coinsHash, coinsErr := send(t, mock, coinsTx(cfg, hot, blockedBTCAddr)) + evmHash, evmErr := send(t, mock, evmTx(cfg, hot, blockedETHAddr)) + if wantBlocked { + require.True(t, isBlockedErr(coinsErr), "%s height=%d coins 应拦截, err=%v", via, next, coinsErr) + require.True(t, isBlockedErr(evmErr), "%s height=%d EVM 应拦截, err=%v", via, next, evmErr) + return nil + } + require.NoError(t, coinsErr, "%s height=%d coins 应放行", via, next) + require.NoError(t, evmErr, "%s height=%d EVM 应放行", via, next) + return []namedHash{{via + " coins", coinsHash}, {via + " EVM", evmHash}} +} + +func waitOnChain(t *testing.T, mock *testnode.Chain33Mock, hash []byte, kind string) { + t.Helper() + detail, err := mock.WaitTx(hash) + require.NoError(t, err, "%s 必须能上链", kind) + require.NotNil(t, detail, "%s WaitTx 无详情", kind) + require.NotNil(t, detail.Receipt, "%s 上链后必须有 receipt", kind) +} + +func isBlockedErr(err error) bool { + return err != nil && (errors.Is(err, types.ErrBlockedAccount) || + strings.Contains(err.Error(), types.ErrBlockedAccount.Error())) +} + +func mineTo(t *testing.T, mock *testnode.Chain33Mock, lastHeight int64) { + t.Helper() + cfg := mock.GetClient().GetConfig() + for { + header, err := mock.GetAPI().GetLastHeader() + require.NoError(t, err) + if header.Height >= lastHeight { + return + } + tx := util.CreateNoneTx(cfg, mock.GetHotKey()) + reply, err := mock.GetAPI().SendTx(tx) + require.NoError(t, err, "推进高度失败 current=%d target=%d", header.Height, lastHeight) + mock.SetLastSend(reply.GetMsg()) + _, err = mock.WaitTx(reply.GetMsg()) + require.NoError(t, err) + } +} + +func assertNextInWindow(t *testing.T, mock *testnode.Chain33Mock, atLeast, before int64) { + t.Helper() + next := nextHeight(t, mock) + require.GreaterOrEqual(t, next, atLeast, "尚未到达目标高度") + require.Less(t, next, before, "已经跨过当前分叉窗口 next=%d", next) +} + +func nextHeight(t *testing.T, mock *testnode.Chain33Mock) int64 { + t.Helper() + header, err := mock.GetAPI().GetLastHeader() + require.NoError(t, err) + return header.Height + 1 +} + +func sendTx(t *testing.T, mock *testnode.Chain33Mock, tx *types.Transaction) ([]byte, error) { + t.Helper() + reply, err := mock.GetAPI().SendTx(tx) + if err != nil { + return nil, err + } + mock.SetLastSend(reply.GetMsg()) + return reply.GetMsg(), nil +} + +type sendTxFn func(*testing.T, *testnode.Chain33Mock, *types.Transaction) ([]byte, error) + +func sendTxRPC(t *testing.T, mock *testnode.Chain33Mock, tx *types.Transaction) ([]byte, error) { + t.Helper() + jsonc := mock.GetJSONC() + require.NotNil(t, jsonc, "JSON-RPC 客户端未就绪") + var txhash string + err := jsonc.Call("Chain33.SendTransaction", &rpctypes.RawParm{Data: common.ToHex(types.Encode(tx))}, &txhash) + if err != nil { + return nil, err + } + hash, err := common.FromHex(txhash) + if err != nil { + return nil, err + } + mock.SetLastSend(hash) + return hash, nil +} + +func coinsTx(cfg *types.Chain33Config, priv crypto.PrivKey, to string) *types.Transaction { + return util.CreateCoinsTx(cfg, priv, to, types.DefaultCoinPrecision) +} + +func evmTx(cfg *types.Chain33Config, priv crypto.PrivKey, contractAddr string) *types.Transaction { + execAddr := address.ExecAddress("evm") + action := &types.EVMContractAction4Chain33{ + Amount: 0, + GasLimit: 10000, + GasPrice: 1, + ContractAddr: contractAddr, + } + tx := &types.Transaction{ + ChainID: cfg.GetChainID(), + Execer: []byte("evm"), + Payload: types.Encode(action), + Fee: 1e6, + To: execAddr, + Nonce: rand.Int63(), + } + tx.Sign(types.SECP256K1, priv) + return tx +} From d3d3347ecd4c735ef5ba0ebd9a646f2774f89405 Mon Sep 17 00:00:00 2001 From: bigsea <601276804@qq.com> Date: Fri, 11 Sep 2026 16:26:00 +0800 Subject: [PATCH 6/7] test(evm): isolate node blacklist test to avoid double Register Keep the testnode case in its own package so CI no longer panics when executor unit tests already registered the evm driver. Co-authored-by: Cursor --- .../evm/executor/{ => nodetest}/blacklist_fork_node_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename plugin/dapp/evm/executor/{ => nodetest}/blacklist_fork_node_test.go (97%) diff --git a/plugin/dapp/evm/executor/blacklist_fork_node_test.go b/plugin/dapp/evm/executor/nodetest/blacklist_fork_node_test.go similarity index 97% rename from plugin/dapp/evm/executor/blacklist_fork_node_test.go rename to plugin/dapp/evm/executor/nodetest/blacklist_fork_node_test.go index 793c4583c..7b59fce75 100644 --- a/plugin/dapp/evm/executor/blacklist_fork_node_test.go +++ b/plugin/dapp/evm/executor/nodetest/blacklist_fork_node_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package executor_test +package nodetest import ( "errors" @@ -22,6 +22,9 @@ import ( "github.com/stretchr/testify/require" ) +// 独立成包,避免和 executor 包内直接调用 Init()/drivers.Register 的单测 +// 共享同一个 test binary,触发 “Register called twice for driver evm”。 +// // 本文件走 testnode 完整节点栈,依赖 plugin/go.mod 中的 github.com/33cn/chain33 // (当前为 v1.71.0,已含 mver 多版本黑名单)。 // From 3080ced4f02c76c6cf7ae5f559dd9b602fd785e0 Mon Sep 17 00:00:00 2001 From: bigsea <601276804@qq.com> Date: Fri, 11 Sep 2026 16:33:44 +0800 Subject: [PATCH 7/7] test(evm): drop testnode blacklist case from CI The full-node test is too heavy for go test ./... and is not needed in this package. Co-authored-by: Cursor --- .../nodetest/blacklist_fork_node_test.go | 292 ------------------ 1 file changed, 292 deletions(-) delete mode 100644 plugin/dapp/evm/executor/nodetest/blacklist_fork_node_test.go diff --git a/plugin/dapp/evm/executor/nodetest/blacklist_fork_node_test.go b/plugin/dapp/evm/executor/nodetest/blacklist_fork_node_test.go deleted file mode 100644 index 7b59fce75..000000000 --- a/plugin/dapp/evm/executor/nodetest/blacklist_fork_node_test.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright Fuzamei Corp. 2018 All Rights Reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package nodetest - -import ( - "errors" - "math/rand" - "strings" - "testing" - - "github.com/33cn/chain33/common" - "github.com/33cn/chain33/common/address" - "github.com/33cn/chain33/common/crypto" - rpctypes "github.com/33cn/chain33/rpc/types" - _ "github.com/33cn/chain33/system" - "github.com/33cn/chain33/types" - "github.com/33cn/chain33/util" - "github.com/33cn/chain33/util/testnode" - _ "github.com/33cn/plugin/plugin/dapp/evm" - "github.com/stretchr/testify/require" -) - -// 独立成包,避免和 executor 包内直接调用 Init()/drivers.Register 的单测 -// 共享同一个 test binary,触发 “Register called twice for driver evm”。 -// -// 本文件走 testnode 完整节点栈,依赖 plugin/go.mod 中的 github.com/33cn/chain33 -// (当前为 v1.71.0,已含 mver 多版本黑名单)。 -// -// 场景:节点按 mver 启动后 -// V1 拦截指定地址 → V2 空名单解除拦截 → V3 把同一地址重新拉黑。 -// coins 与 EVM(ContractAddr 命中)都走 queue SendTx 和 JSON-RPC SendTransaction: -// 拦截时 mempool 拒绝,放行时 WaitTx 确认上链。 -// solo 只在 mempool 有交易时出块,跨高度用 none 交易推进,不能空等。 -// 创世地址 14KEKbYtKKQm4wMthSK9J4La4nAiidGozt 不能进名单,否则出块和 SendHot 会被自己拦住。 - -const ( - forkV1Height = int64(2) - forkV2Height = int64(5) - forkV3Height = int64(15) - - // TestPrivkeyList[2],不是 genesis / hot - blockedBTCAddr = "1EbDHAXpoiewjPLX9uqoz38HsKqMXayZrF" - blockedETHAddr = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0" - - defaultBlacklistSection = "[mver.blacklist]\naccountBlacklist=[]\n" -) - -func TestNodeBlacklistV2ClearThenV3Restore(t *testing.T) { - cfg := newNodeBlacklistCfg(t) - mock := testnode.NewWithConfig(cfg, nil) - defer mock.Close() - mock.Listen() - - require.NoError(t, mock.SendHot(), "genesis 转给 hot 必须在名单之外") - - t.Run("节点启动后配置快照", func(t *testing.T) { - assertCfgSnapshot(t, cfg) - }) - - t.Run("V1 生效后拦截", func(t *testing.T) { - mineTo(t, mock, forkV1Height-1) - assertNextInWindow(t, mock, forkV1Height, forkV2Height) - assertSendBlocked(t, mock, true) - }) - - t.Run("V2 空名单后放行", func(t *testing.T) { - mineTo(t, mock, forkV2Height) - assertNextInWindow(t, mock, forkV2Height, forkV3Height) - assertSendBlocked(t, mock, false) - }) - - t.Run("V3 重新拉黑后拦截", func(t *testing.T) { - mineTo(t, mock, forkV3Height) - require.GreaterOrEqual(t, nextHeight(t, mock), forkV3Height) - assertSendBlocked(t, mock, true) - }) -} - -func newNodeBlacklistCfg(t *testing.T) *types.Chain33Config { - t.Helper() - cfgstring := types.GetDefaultCfgstring() - cfgstring = strings.Replace(cfgstring, `Title="local"`, `Title="chain33"`+"\nDisableForkCheck=true", 1) - cfgstring = strings.Replace(cfgstring, defaultBlacklistSection, "", 1) - cfgstring += nodeBlacklistTOML() - // V2/V3 未在 RegisterSystemFork 里登记,toml 里写出高度后由 initForkConfig 注入。 - // Title 不能是 local,否则 SetAllFork(0) 会把三个版本压到同一高度,只留下 V3。 - return types.NewChain33Config(cfgstring) -} - -func nodeBlacklistTOML() string { - return ` -[mver.blacklist] -accountBlacklist=[] -[mver.blacklist.ForkAccountBlacklist] -accountBlacklist=["` + blockedBTCAddr + `","` + blockedETHAddr + `"] -[mver.blacklist.ForkAccountBlacklistV2] -accountBlacklist=[] -[mver.blacklist.ForkAccountBlacklistV3] -accountBlacklist=["` + blockedBTCAddr + `","` + blockedETHAddr + `"] - -[fork.system] -ForkAccountBlacklist=2 -ForkAccountBlacklistV2=5 -ForkAccountBlacklistV3=15 - -[fork.sub.coins] -Enable=0 -ForkFriendExecer=0 - -[fork.sub.evm] -Enable=0 -` -} - -func assertCfgSnapshot(t *testing.T, cfg *types.Chain33Config) { - t.Helper() - hot := address.PubKeyToAddr(address.DefaultID, util.TestPrivkeyList[0].PubKey().Bytes()) - priv := util.TestPrivkeyList[0] - - cases := []struct { - name string - height int64 - blocked bool - }{ - {"V1 前放行", forkV1Height - 1, false}, - {"V1 拦截", forkV1Height, true}, - {"V2 前仍拦截", forkV2Height - 1, true}, - {"V2 空名单放行", forkV2Height, false}, - {"V3 前仍放行", forkV3Height - 1, false}, - {"V3 重新拦截", forkV3Height, true}, - } - for _, c := range cases { - c := c - t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.blocked, cfg.IsBlockedAccount(blockedBTCAddr, c.height), "btc height=%d", c.height) - require.Equal(t, c.blocked, cfg.IsBlockedAccount(blockedETHAddr, c.height), "eth height=%d", c.height) - require.False(t, cfg.IsBlockedAccount(hot, c.height), "hot 不得进名单") - - coinsErr := types.CheckTxBlockedAccount(cfg, c.height, coinsTx(cfg, priv, blockedBTCAddr)) - evmErr := types.CheckTxBlockedAccount(cfg, c.height, evmTx(cfg, priv, blockedETHAddr)) - if c.blocked { - require.Error(t, coinsErr) - require.Error(t, evmErr) - require.True(t, errors.Is(coinsErr, types.ErrBlockedAccount)) - require.True(t, errors.Is(evmErr, types.ErrBlockedAccount)) - return - } - require.NoError(t, coinsErr) - require.NoError(t, evmErr) - }) - } -} - -func assertSendBlocked(t *testing.T, mock *testnode.Chain33Mock, wantBlocked bool) { - t.Helper() - next := nextHeight(t, mock) - var onChain []namedHash - t.Run("queue", func(t *testing.T) { - onChain = append(onChain, assertSendVia(t, mock, next, wantBlocked, "queue", sendTx)...) - }) - t.Run("jsonrpc", func(t *testing.T) { - onChain = append(onChain, assertSendVia(t, mock, next, wantBlocked, "jsonrpc", sendTxRPC)...) - }) - if wantBlocked { - return - } - for _, item := range onChain { - waitOnChain(t, mock, item.hash, item.kind) - } -} - -type namedHash struct { - kind string - hash []byte -} - -func assertSendVia(t *testing.T, mock *testnode.Chain33Mock, next int64, wantBlocked bool, via string, send sendTxFn) []namedHash { - t.Helper() - cfg := mock.GetClient().GetConfig() - hot := mock.GetHotKey() - - coinsHash, coinsErr := send(t, mock, coinsTx(cfg, hot, blockedBTCAddr)) - evmHash, evmErr := send(t, mock, evmTx(cfg, hot, blockedETHAddr)) - if wantBlocked { - require.True(t, isBlockedErr(coinsErr), "%s height=%d coins 应拦截, err=%v", via, next, coinsErr) - require.True(t, isBlockedErr(evmErr), "%s height=%d EVM 应拦截, err=%v", via, next, evmErr) - return nil - } - require.NoError(t, coinsErr, "%s height=%d coins 应放行", via, next) - require.NoError(t, evmErr, "%s height=%d EVM 应放行", via, next) - return []namedHash{{via + " coins", coinsHash}, {via + " EVM", evmHash}} -} - -func waitOnChain(t *testing.T, mock *testnode.Chain33Mock, hash []byte, kind string) { - t.Helper() - detail, err := mock.WaitTx(hash) - require.NoError(t, err, "%s 必须能上链", kind) - require.NotNil(t, detail, "%s WaitTx 无详情", kind) - require.NotNil(t, detail.Receipt, "%s 上链后必须有 receipt", kind) -} - -func isBlockedErr(err error) bool { - return err != nil && (errors.Is(err, types.ErrBlockedAccount) || - strings.Contains(err.Error(), types.ErrBlockedAccount.Error())) -} - -func mineTo(t *testing.T, mock *testnode.Chain33Mock, lastHeight int64) { - t.Helper() - cfg := mock.GetClient().GetConfig() - for { - header, err := mock.GetAPI().GetLastHeader() - require.NoError(t, err) - if header.Height >= lastHeight { - return - } - tx := util.CreateNoneTx(cfg, mock.GetHotKey()) - reply, err := mock.GetAPI().SendTx(tx) - require.NoError(t, err, "推进高度失败 current=%d target=%d", header.Height, lastHeight) - mock.SetLastSend(reply.GetMsg()) - _, err = mock.WaitTx(reply.GetMsg()) - require.NoError(t, err) - } -} - -func assertNextInWindow(t *testing.T, mock *testnode.Chain33Mock, atLeast, before int64) { - t.Helper() - next := nextHeight(t, mock) - require.GreaterOrEqual(t, next, atLeast, "尚未到达目标高度") - require.Less(t, next, before, "已经跨过当前分叉窗口 next=%d", next) -} - -func nextHeight(t *testing.T, mock *testnode.Chain33Mock) int64 { - t.Helper() - header, err := mock.GetAPI().GetLastHeader() - require.NoError(t, err) - return header.Height + 1 -} - -func sendTx(t *testing.T, mock *testnode.Chain33Mock, tx *types.Transaction) ([]byte, error) { - t.Helper() - reply, err := mock.GetAPI().SendTx(tx) - if err != nil { - return nil, err - } - mock.SetLastSend(reply.GetMsg()) - return reply.GetMsg(), nil -} - -type sendTxFn func(*testing.T, *testnode.Chain33Mock, *types.Transaction) ([]byte, error) - -func sendTxRPC(t *testing.T, mock *testnode.Chain33Mock, tx *types.Transaction) ([]byte, error) { - t.Helper() - jsonc := mock.GetJSONC() - require.NotNil(t, jsonc, "JSON-RPC 客户端未就绪") - var txhash string - err := jsonc.Call("Chain33.SendTransaction", &rpctypes.RawParm{Data: common.ToHex(types.Encode(tx))}, &txhash) - if err != nil { - return nil, err - } - hash, err := common.FromHex(txhash) - if err != nil { - return nil, err - } - mock.SetLastSend(hash) - return hash, nil -} - -func coinsTx(cfg *types.Chain33Config, priv crypto.PrivKey, to string) *types.Transaction { - return util.CreateCoinsTx(cfg, priv, to, types.DefaultCoinPrecision) -} - -func evmTx(cfg *types.Chain33Config, priv crypto.PrivKey, contractAddr string) *types.Transaction { - execAddr := address.ExecAddress("evm") - action := &types.EVMContractAction4Chain33{ - Amount: 0, - GasLimit: 10000, - GasPrice: 1, - ContractAddr: contractAddr, - } - tx := &types.Transaction{ - ChainID: cfg.GetChainID(), - Execer: []byte("evm"), - Payload: types.Encode(action), - Fee: 1e6, - To: execAddr, - Nonce: rand.Int63(), - } - tx.Sign(types.SECP256K1, priv) - return tx -}