From d1380920dbe17296b0f7adcd3f701cfd87432f42 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Tue, 11 Nov 2025 20:11:14 +0900 Subject: [PATCH 01/30] feat: enforce blacklist in txpool validation --- core/txpool/blobpool/blobpool.go | 2 ++ core/txpool/errors.go | 4 ++++ core/txpool/legacypool/legacypool.go | 2 ++ core/txpool/validation.go | 18 +++++++++++++++++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 48631dfd8..78f6801ed 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1098,6 +1098,8 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error { } // Ensure the transaction adheres to the stateful pool filters (nonce, balance) stateOpts := &txpool.ValidationOptionsWithState{ + Config: p.chain.Config(), + State: p.state, FirstNonceGap: func(addr common.Address) uint64 { diff --git a/core/txpool/errors.go b/core/txpool/errors.go index 55bfe9e9e..d0976e238 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -72,4 +72,8 @@ var ( // WEMIX ErrSenderInsufficientFunds is returned if the value cost of executing a transaction // is higher than the balance of the sender's account. ErrSenderInsufficientFunds = errors.New("fee delegation: insufficient sender's funds for value") + + // ErrBlacklistedAccount is returned if a transaction involves an account + // that is blacklisted. + ErrBlacklistedAccount = errors.New("account is blacklisted") ) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index a1f67227a..7c0a06b14 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -633,6 +633,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro // rules and adheres to some heuristic limits of the local node (price and size). func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { opts := &txpool.ValidationOptionsWithState{ + Config: pool.chainconfig, + State: pool.currentState, FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 426432168..21acb1acb 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -186,6 +186,8 @@ func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) err // ValidationOptionsWithState define certain differences between stateful transaction // validation across the different pools without having to duplicate those checks. type ValidationOptionsWithState struct { + Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules + State *state.StateDB // State database to check nonces and balances against // FirstNonceGap is an optional callback to retrieve the first nonce gap in @@ -220,6 +222,15 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op log.Error("Transaction sender recovery failed", "err", err) return err } + // Ensure that neither the sender nor the recipient is blacklisted + if opts.Config.AnzeonEnabled() { + if opts.State.IsBlacklisted(from) { + return fmt.Errorf("%w: from %v", ErrBlacklistedAccount, from.Hex()) + } + if to := tx.To(); to != nil && opts.State.IsBlacklisted(*to) { + return fmt.Errorf("%w: to %v", ErrBlacklistedAccount, to.Hex()) + } + } next := opts.State.GetNonce(from) if next > tx.Nonce() { return fmt.Errorf("%w: next nonce %v, tx nonce %v", core.ErrNonceTooLow, next, tx.Nonce()) @@ -233,10 +244,15 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op } // Ensure the transactor has enough funds to cover the transaction costs if tx.Type() == types.FeeDelegateDynamicFeeTxType { + feePayer := tx.FeePayer() + // Ensure that the fee payer is not blacklisted + if opts.Config.AnzeonEnabled() && opts.State.IsBlacklisted(*feePayer) { + return fmt.Errorf("%w: feePayer: %v", ErrBlacklistedAccount, feePayer.Hex()) + } if opts.State.GetBalance(from).ToBig().Cmp(tx.Value()) < 0 { return ErrSenderInsufficientFunds } - if opts.State.GetBalance(*tx.FeePayer()).ToBig().Cmp(tx.FeeCost()) < 0 { + if opts.State.GetBalance(*feePayer).ToBig().Cmp(tx.FeeCost()) < 0 { return ErrFeePayerInsufficientFunds } } else { From 9f6d0ae44a078d728d6a1fb43d77ad9b9f01341f Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Wed, 12 Nov 2025 15:39:56 +0900 Subject: [PATCH 02/30] feat: enforce blacklist in EVM Call and Create --- core/vm/errors.go | 3 +++ core/vm/evm.go | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/core/vm/errors.go b/core/vm/errors.go index b2d93195b..af0fee782 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -47,6 +47,9 @@ var ( // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. errStopToken = errors.New("stop token") + + // ErrBlacklistedAccount indicates EVM execution with a blacklisted account + ErrBlacklistedAccount = errors.New("account is blacklisted") ) // ErrStackUnderflow wraps an evm error when the items on the stack less diff --git a/core/vm/evm.go b/core/vm/evm.go index bc592280e..03468c4c6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,6 +17,7 @@ package vm import ( + "fmt" "math/big" "sync/atomic" @@ -195,6 +196,15 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + // Fail if the caller or the target address is blacklisted under Anzeon rules + if evm.chainRules.IsAnzeon { + if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { + return nil, gas, fmt.Errorf("%w: from %v", ErrBlacklistedAccount, callerAddr.Hex()) + } + if evm.StateDB.IsBlacklisted(addr) { + return nil, gas, fmt.Errorf("%w: to %v", ErrBlacklistedAccount, addr.Hex()) + } + } m, isNativeManager := evm.nativeManager(addr) p, isPrecompile := evm.precompile(addr) if !value.IsZero() { @@ -462,6 +472,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.depth > int(params.CallCreateDepth) { return nil, common.Address{}, gas, ErrDepth } + // Fail if the caller is blacklisted under Anzeon rules + if evm.chainRules.IsAnzeon && evm.StateDB.IsBlacklisted(caller.Address()) { + return nil, common.Address{}, gas, fmt.Errorf("%w: from %v", ErrBlacklistedAccount, caller.Address().Hex()) + } if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance } From 66def00edc4c51423390003d5a381ebdf153ebf1 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Wed, 12 Nov 2025 17:46:54 +0900 Subject: [PATCH 03/30] feat: enforce blacklist in opSelfdestruct and opSelfdestruct6780 --- core/vm/instructions.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b8055de6b..df0fce30a 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,6 +17,7 @@ package vm import ( + "fmt" "math" "github.com/ethereum/go-ethereum/common" @@ -801,9 +802,22 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext return nil, ErrWriteProtection } beneficiary := scope.Stack.pop() + // Deny SELFDESTRUCT if the contract or beneficiary is blacklisted under Anzeon rules + if interpreter.evm.chainRules.IsAnzeon { + if contractAddr := scope.Contract.Address(); interpreter.evm.StateDB.IsBlacklisted(contractAddr) { + return nil, fmt.Errorf("%w: contract %v", ErrBlacklistedAccount, contractAddr.Hex()) + } + if beneficiaryAddr := beneficiary.Bytes20(); interpreter.evm.StateDB.IsBlacklisted(beneficiaryAddr) { + return nil, fmt.Errorf("%w: beneficiary %v", ErrBlacklistedAccount, common.Address(beneficiaryAddr).Hex()) + } + } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) + // Emit a transfer log if balance is non-zero + if !balance.IsZero() { + interpreter.evm.AddTransferLog(scope.Contract.Address(), beneficiary.Bytes20(), balance) + } if tracer := interpreter.evm.Config.Tracer; tracer != nil { tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) tracer.CaptureExit([]byte{}, 0, nil) @@ -816,9 +830,22 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon return nil, ErrWriteProtection } beneficiary := scope.Stack.pop() + // Deny SELFDESTRUCT if the contract or beneficiary is blacklisted under Anzeon rules + if interpreter.evm.chainRules.IsAnzeon { + if contractAddr := scope.Contract.Address(); interpreter.evm.StateDB.IsBlacklisted(contractAddr) { + return nil, fmt.Errorf("%w: contract %v", ErrBlacklistedAccount, contractAddr.Hex()) + } + if beneficiaryAddr := beneficiary.Bytes20(); interpreter.evm.StateDB.IsBlacklisted(beneficiaryAddr) { + return nil, fmt.Errorf("%w: beneficiary %v", ErrBlacklistedAccount, common.Address(beneficiaryAddr).Hex()) + } + } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) + // Emit a transfer log if balance is non-zero + if !balance.IsZero() { + interpreter.evm.AddTransferLog(scope.Contract.Address(), beneficiary.Bytes20(), balance) + } interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) if tracer := interpreter.evm.Config.Tracer; tracer != nil { tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance.ToBig()) From 20ee28a29e02efa241cc0efd5a31c08d27f1696f Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Thu, 13 Nov 2025 14:17:27 +0900 Subject: [PATCH 04/30] feat: enforce blacklist checks for block signer --- consensus/wbft/common/errors.go | 6 ++++++ consensus/wbft/engine/engine.go | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/consensus/wbft/common/errors.go b/consensus/wbft/common/errors.go index e495ac906..5f13cc818 100644 --- a/consensus/wbft/common/errors.go +++ b/consensus/wbft/common/errors.go @@ -143,4 +143,10 @@ var ( ErrIsNotWBFTBlock = errors.New("block is not a wbft block") ErrEpochInfoIsNotNil = errors.New("epoch info should be nil for non-epoch block") + + // ErrStateNotFound is returned when the state for the given root is missing. + ErrStateNotFound = errors.New("state not found") + + // ErrBlacklistedSigner is returned when a block is signed by a blacklisted account. + ErrBlacklistedSigner = errors.New("signer is blacklisted") ) diff --git a/consensus/wbft/engine/engine.go b/consensus/wbft/engine/engine.go index 38c6cbc63..c49bf9bd0 100644 --- a/consensus/wbft/engine/engine.go +++ b/consensus/wbft/engine/engine.go @@ -356,6 +356,26 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H return err } + // Check parent + var parent *types.Header + if len(parents) > 0 { + parent = parents[len(parents)-1] + } else { + parent = chain.GetHeader(header.ParentHash, number-1) + } + // Ensure that the block's parent has right number and hash + if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { + return consensus.ErrUnknownAncestor + } + + state, err := chain.StateAt(parent.Root) + if err != nil { + return fmt.Errorf("%w: parent %v", wbftcommon.ErrStateNotFound, parent.Hash().Hex()) + } + if state.IsBlacklisted(signer) { + return fmt.Errorf("%w: signer %v", wbftcommon.ErrBlacklistedSigner, signer.Hex()) + } + // Signer should be in the validator set of previous block's extraData. if _, v := validators.GetByAddress(signer); v == nil { return wbftcommon.ErrUnauthorized From 9f635c1dd9c7b8341b743719443bd030bfb46cae Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Thu, 13 Nov 2025 14:27:27 +0900 Subject: [PATCH 05/30] fix: fix minor typos --- consensus/wbft/backend/backend.go | 4 ++-- consensus/wbft/core/types.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/consensus/wbft/backend/backend.go b/consensus/wbft/backend/backend.go index 01c4bd64b..a6de34bac 100644 --- a/consensus/wbft/backend/backend.go +++ b/consensus/wbft/backend/backend.go @@ -302,8 +302,8 @@ func (sb *Backend) CheckSignature(data []byte, address common.Address, sig []byt return nil } -// HasPropsal implements wbft.Backend.HashBlock -func (sb *Backend) HasPropsal(hash common.Hash, number *big.Int) bool { +// HasProposal implements wbft.Backend.HashBlock +func (sb *Backend) HasProposal(hash common.Hash, number *big.Int) bool { return sb.chain.GetHeader(hash, number.Uint64()) != nil } diff --git a/consensus/wbft/core/types.go b/consensus/wbft/core/types.go index ed8f66cec..8db08309c 100644 --- a/consensus/wbft/core/types.go +++ b/consensus/wbft/core/types.go @@ -127,8 +127,8 @@ type Backend interface { // LastProposal retrieves latest committed proposal and the address of proposer LastProposal() (wbft.Proposal, common.Address) - // HasPropsal checks if the combination of the given hash and height matches any existing blocks - HasPropsal(hash common.Hash, number *big.Int) bool + // HasProposal checks if the combination of the given hash and height matches any existing blocks + HasProposal(hash common.Hash, number *big.Int) bool // GetProposer returns the proposer of the given block height GetProposer(number uint64) common.Address From 652d5732d7d156c8917840d1a7b3bce5dbd50132 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Thu, 13 Nov 2025 14:50:58 +0900 Subject: [PATCH 06/30] refactor: use %s format for hex addresses --- consensus/wbft/engine/engine.go | 4 ++-- core/txpool/validation.go | 6 +++--- core/vm/evm.go | 6 +++--- core/vm/instructions.go | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/consensus/wbft/engine/engine.go b/consensus/wbft/engine/engine.go index c49bf9bd0..ae5cc461e 100644 --- a/consensus/wbft/engine/engine.go +++ b/consensus/wbft/engine/engine.go @@ -370,10 +370,10 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H state, err := chain.StateAt(parent.Root) if err != nil { - return fmt.Errorf("%w: parent %v", wbftcommon.ErrStateNotFound, parent.Hash().Hex()) + return fmt.Errorf("%w: parent %s", wbftcommon.ErrStateNotFound, parent.Hash().Hex()) } if state.IsBlacklisted(signer) { - return fmt.Errorf("%w: signer %v", wbftcommon.ErrBlacklistedSigner, signer.Hex()) + return fmt.Errorf("%w: signer %s", wbftcommon.ErrBlacklistedSigner, signer.Hex()) } // Signer should be in the validator set of previous block's extraData. diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 21acb1acb..a5d4e9c4d 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -225,10 +225,10 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op // Ensure that neither the sender nor the recipient is blacklisted if opts.Config.AnzeonEnabled() { if opts.State.IsBlacklisted(from) { - return fmt.Errorf("%w: from %v", ErrBlacklistedAccount, from.Hex()) + return fmt.Errorf("%w: from %s", ErrBlacklistedAccount, from.Hex()) } if to := tx.To(); to != nil && opts.State.IsBlacklisted(*to) { - return fmt.Errorf("%w: to %v", ErrBlacklistedAccount, to.Hex()) + return fmt.Errorf("%w: to %s", ErrBlacklistedAccount, to.Hex()) } } next := opts.State.GetNonce(from) @@ -247,7 +247,7 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op feePayer := tx.FeePayer() // Ensure that the fee payer is not blacklisted if opts.Config.AnzeonEnabled() && opts.State.IsBlacklisted(*feePayer) { - return fmt.Errorf("%w: feePayer: %v", ErrBlacklistedAccount, feePayer.Hex()) + return fmt.Errorf("%w: fee payer: %s", ErrBlacklistedAccount, feePayer.Hex()) } if opts.State.GetBalance(from).ToBig().Cmp(tx.Value()) < 0 { return ErrSenderInsufficientFunds diff --git a/core/vm/evm.go b/core/vm/evm.go index 03468c4c6..401e5eb12 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -199,10 +199,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // Fail if the caller or the target address is blacklisted under Anzeon rules if evm.chainRules.IsAnzeon { if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { - return nil, gas, fmt.Errorf("%w: from %v", ErrBlacklistedAccount, callerAddr.Hex()) + return nil, gas, fmt.Errorf("%w: caller %s", ErrBlacklistedAccount, callerAddr.Hex()) } if evm.StateDB.IsBlacklisted(addr) { - return nil, gas, fmt.Errorf("%w: to %v", ErrBlacklistedAccount, addr.Hex()) + return nil, gas, fmt.Errorf("%w: target %s", ErrBlacklistedAccount, addr.Hex()) } } m, isNativeManager := evm.nativeManager(addr) @@ -474,7 +474,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Fail if the caller is blacklisted under Anzeon rules if evm.chainRules.IsAnzeon && evm.StateDB.IsBlacklisted(caller.Address()) { - return nil, common.Address{}, gas, fmt.Errorf("%w: from %v", ErrBlacklistedAccount, caller.Address().Hex()) + return nil, common.Address{}, gas, fmt.Errorf("%w: caller %s", ErrBlacklistedAccount, caller.Address().Hex()) } if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance diff --git a/core/vm/instructions.go b/core/vm/instructions.go index df0fce30a..9d5b0ee60 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -805,10 +805,10 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext // Deny SELFDESTRUCT if the contract or beneficiary is blacklisted under Anzeon rules if interpreter.evm.chainRules.IsAnzeon { if contractAddr := scope.Contract.Address(); interpreter.evm.StateDB.IsBlacklisted(contractAddr) { - return nil, fmt.Errorf("%w: contract %v", ErrBlacklistedAccount, contractAddr.Hex()) + return nil, fmt.Errorf("%w: contract %s", ErrBlacklistedAccount, contractAddr.Hex()) } if beneficiaryAddr := beneficiary.Bytes20(); interpreter.evm.StateDB.IsBlacklisted(beneficiaryAddr) { - return nil, fmt.Errorf("%w: beneficiary %v", ErrBlacklistedAccount, common.Address(beneficiaryAddr).Hex()) + return nil, fmt.Errorf("%w: beneficiary %s", ErrBlacklistedAccount, common.Address(beneficiaryAddr).Hex()) } } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) @@ -833,10 +833,10 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon // Deny SELFDESTRUCT if the contract or beneficiary is blacklisted under Anzeon rules if interpreter.evm.chainRules.IsAnzeon { if contractAddr := scope.Contract.Address(); interpreter.evm.StateDB.IsBlacklisted(contractAddr) { - return nil, fmt.Errorf("%w: contract %v", ErrBlacklistedAccount, contractAddr.Hex()) + return nil, fmt.Errorf("%w: contract %s", ErrBlacklistedAccount, contractAddr.Hex()) } if beneficiaryAddr := beneficiary.Bytes20(); interpreter.evm.StateDB.IsBlacklisted(beneficiaryAddr) { - return nil, fmt.Errorf("%w: beneficiary %v", ErrBlacklistedAccount, common.Address(beneficiaryAddr).Hex()) + return nil, fmt.Errorf("%w: beneficiary %s", ErrBlacklistedAccount, common.Address(beneficiaryAddr).Hex()) } } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) From 3424c384cf0ad8358607f3fd4fb8e4e2a7e0710e Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Thu, 13 Nov 2025 15:23:41 +0900 Subject: [PATCH 07/30] feat: add AccountManagerAddress to AccessList in StateDB Prepare --- core/state/statedb.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index 794bbe928..27e83682a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1388,6 +1388,7 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d if rules.IsAnzeon { al.AddAddress(params.NativeCoinManagerAddress) + al.AddAddress(params.AccountManagerAddress) } } // Reset transient storage at the beginning of transaction execution From bdbb68a316937713058c9113ec54fd70bb102fda Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Thu, 13 Nov 2025 15:35:09 +0900 Subject: [PATCH 08/30] chore: clean up duplicate copyright line --- systemcontracts/contracts.go | 1 - systemcontracts/systemcontracts.go | 1 - 2 files changed, 2 deletions(-) diff --git a/systemcontracts/contracts.go b/systemcontracts/contracts.go index 6dce59330..164f47f46 100644 --- a/systemcontracts/contracts.go +++ b/systemcontracts/contracts.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright 2025 The go-stablenet Authors -// Copyright 2025 The go-stablenet Authors // This file is part of the go-stablenet library. // // The go-stablenet library is free software: you can redistribute it and/or modify diff --git a/systemcontracts/systemcontracts.go b/systemcontracts/systemcontracts.go index 3284ab490..678b05633 100644 --- a/systemcontracts/systemcontracts.go +++ b/systemcontracts/systemcontracts.go @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-or-later // Copyright 2025 The go-stablenet Authors -// Copyright 2025 The go-stablenet Authors // This file is part of the go-stablenet library. // // The go-stablenet library is free software: you can redistribute it and/or modify From d323ca457cc6441fddb3e7fde4066321b7eb3dfb Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Fri, 14 Nov 2025 11:29:07 +0900 Subject: [PATCH 09/30] test: add transaction validation tests for blacklisted accounts --- core/txpool/validation.go | 2 +- core/txpool/validation_test.go | 305 +++++++++++++++++++++++++++++++++ 2 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 core/txpool/validation_test.go diff --git a/core/txpool/validation.go b/core/txpool/validation.go index a5d4e9c4d..dbbad4a35 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -247,7 +247,7 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op feePayer := tx.FeePayer() // Ensure that the fee payer is not blacklisted if opts.Config.AnzeonEnabled() && opts.State.IsBlacklisted(*feePayer) { - return fmt.Errorf("%w: fee payer: %s", ErrBlacklistedAccount, feePayer.Hex()) + return fmt.Errorf("%w: fee payer %s", ErrBlacklistedAccount, feePayer.Hex()) } if opts.State.GetBalance(from).ToBig().Cmp(tx.Value()) < 0 { return ErrSenderInsufficientFunds diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go new file mode 100644 index 000000000..c5b7ffec2 --- /dev/null +++ b/core/txpool/validation_test.go @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2025 The go-stablenet Authors +// This file is part of the go-stablenet library. +// +// The go-stablenet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-stablenet library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-stablenet library. If not, see . + +package txpool + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +type account struct { + privKey *ecdsa.PrivateKey + address common.Address +} + +type testAccounts struct { + sender *account + recipient *account + feePayer *account +} + +var ( + chainConfig *params.ChainConfig + signer types.Signer + feeDelegateSigner types.Signer + initialBalance *uint256.Int +) + +func init() { + chainConfig = params.TestWBFTChainConfig + signer = types.LatestSigner(chainConfig) + feeDelegateSigner = types.NewFeeDelegateSigner(chainConfig.ChainID) + initialBalance = uint256.MustFromDecimal("1000000000000000000000") +} + +func newStateDB() *state.StateDB { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + return statedb +} + +func newOptions(statedb *state.StateDB) *ValidationOptionsWithState { + opts := &ValidationOptionsWithState{ + Config: params.TestWBFTChainConfig, + State: statedb, + FirstNonceGap: nil, + UsedAndLeftSlots: nil, + ExistingExpenditure: nil, + ExistingCost: nil, + } + return opts +} + +func newTestState() (*state.StateDB, *ValidationOptionsWithState) { + statedb := newStateDB() + return statedb, newOptions(statedb) +} + +func newAccount(statedb *state.StateDB) *account { + key, _ := crypto.GenerateKey() + address := crypto.PubkeyToAddress(key.PublicKey) + + account := &account{key, address} + statedb.CreateAccount(account.address) + statedb.AddBalance(account.address, initialBalance) + + return account +} + +func newBlacklistedAccount(statedb *state.StateDB) *account { + account := newAccount(statedb) + statedb.SetBlacklisted(account.address) + + return account +} + +func newDynamicFeeTx(to *common.Address) types.DynamicFeeTx { + return types.DynamicFeeTx{ + ChainID: chainConfig.ChainID, + Nonce: 0, + GasTipCap: new(big.Int).SetUint64(params.InitialGasTip), + GasFeeCap: new(big.Int).Add(new(big.Int).SetUint64(params.MinBaseFee), new(big.Int).SetUint64(params.InitialGasTip)), + Gas: params.TxGas, + To: to, + Value: big.NewInt(1000000000000000000), + Data: nil, + AccessList: nil, + } +} + +func signDynamicFeeTx(tx types.DynamicFeeTx, sender *account) (*types.Transaction, error) { + return types.SignNewTx(sender.privKey, signer, &tx) +} + +func signFeeDelegateTx(rawTx *types.Transaction, feePayer *account) (*types.Transaction, error) { + V, R, S := rawTx.RawSignatureValues() + senderTx := types.DynamicFeeTx{ + To: rawTx.To(), + ChainID: rawTx.ChainId(), + Nonce: rawTx.Nonce(), + Gas: rawTx.Gas(), + GasFeeCap: rawTx.GasFeeCap(), + GasTipCap: rawTx.GasTipCap(), + Value: rawTx.Value(), + Data: rawTx.Data(), + AccessList: rawTx.AccessList(), + V: V, + R: R, + S: S, + } + + feeDelegateTx := &types.FeeDelegateDynamicFeeTx{ + FeePayer: &feePayer.address, + } + feeDelegateTx.SetSenderTx(senderTx) + tx := types.NewTx(feeDelegateTx) + + return types.SignTx(tx, feeDelegateSigner, feePayer.privKey) +} + +func buildAccounts(statedb *state.StateDB, senderBlacklisted, recipientBlacklisted, feePayerBlacklisted bool) testAccounts { + var accounts testAccounts + + if senderBlacklisted { + accounts.sender = newBlacklistedAccount(statedb) + } else { + accounts.sender = newAccount(statedb) + } + + if recipientBlacklisted { + accounts.recipient = newBlacklistedAccount(statedb) + } else { + accounts.recipient = newAccount(statedb) + } + + if feePayerBlacklisted { + accounts.feePayer = newBlacklistedAccount(statedb) + } else { + accounts.feePayer = newAccount(statedb) + } + + return accounts +} + +func TestBlacklistedAccountTx(t *testing.T) { + t.Run("DynamicFeeTx", func(t *testing.T) { + tests := []struct { + name string + senderBlacklisted bool + recipientBlacklisted bool + expectErr bool + getErrPart func(accts testAccounts) string + }{ + { + name: "unrelated to any blacklisted account", + senderBlacklisted: false, + recipientBlacklisted: false, + expectErr: false, + }, + { + name: "sender is blacklisted", + senderBlacklisted: true, + recipientBlacklisted: false, + expectErr: true, + getErrPart: func(accts testAccounts) string { + return fmt.Sprintf("from %s", accts.sender.address.Hex()) + }, + }, + { + name: "recipient is blacklisted", + senderBlacklisted: false, + recipientBlacklisted: true, + expectErr: true, + getErrPart: func(accts testAccounts) string { + return fmt.Sprintf("to %s", accts.recipient.address.Hex()) + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + statedb, opts := newTestState() + + accts := buildAccounts(statedb, tc.senderBlacklisted, tc.recipientBlacklisted, false) + + sender := accts.sender + recipient := accts.recipient + + tx := newDynamicFeeTx(&recipient.address) + signedTx, _ := signDynamicFeeTx(tx, sender) + + err := ValidateTransactionWithState(signedTx, signer, opts) + if tc.expectErr { + require.ErrorIs(t, err, ErrBlacklistedAccount) + require.Contains(t, err.Error(), tc.getErrPart(accts)) + } else { + require.NoError(t, err) + } + }) + } + }) + + t.Run("FeeDelegateDynamicFeeTx", func(t *testing.T) { + tests := []struct { + name string + senderBlacklisted bool + recipientBlacklisted bool + feePayerBlacklisted bool + expectErr bool + getErrPart func(accts testAccounts) string + }{ + { + name: "unrelated to any blacklisted account", + senderBlacklisted: false, + recipientBlacklisted: false, + feePayerBlacklisted: false, + expectErr: false, + }, + { + name: "sender is blacklisted", + senderBlacklisted: true, + recipientBlacklisted: false, + feePayerBlacklisted: false, + expectErr: true, + getErrPart: func(accts testAccounts) string { + return fmt.Sprintf("from %s", accts.sender.address.Hex()) + }, + }, + { + name: "recipient is blacklisted", + senderBlacklisted: false, + recipientBlacklisted: true, + feePayerBlacklisted: false, + expectErr: true, + getErrPart: func(accts testAccounts) string { + return fmt.Sprintf("to %s", accts.recipient.address.Hex()) + }, + }, + { + name: "fee payer is blacklisted", + senderBlacklisted: false, + recipientBlacklisted: false, + feePayerBlacklisted: true, + expectErr: true, + getErrPart: func(accts testAccounts) string { + return fmt.Sprintf("fee payer %s", accts.feePayer.address.Hex()) + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + statedb, opts := newTestState() + + accts := buildAccounts(statedb, tc.senderBlacklisted, tc.recipientBlacklisted, tc.feePayerBlacklisted) + + sender := accts.sender + recipient := accts.recipient + feePayer := accts.feePayer + + tx := newDynamicFeeTx(&recipient.address) + signedTx, _ := signDynamicFeeTx(tx, sender) + feePayerSignedTx, _ := signFeeDelegateTx(signedTx, feePayer) + + err := ValidateTransactionWithState(feePayerSignedTx, signer, opts) + if tc.expectErr { + require.ErrorIs(t, err, ErrBlacklistedAccount) + require.Contains(t, err.Error(), tc.getErrPart(accts)) + } else { + require.NoError(t, err) + } + }) + } + }) +} From 380fb629940e70366fdc7ab7349effce23176e09 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Fri, 14 Nov 2025 15:05:19 +0900 Subject: [PATCH 10/30] test: add EVM validation tests for blacklisted accounts --- core/vm/evm_test.go | 197 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 core/vm/evm_test.go diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go new file mode 100644 index 000000000..eaf4ca25f --- /dev/null +++ b/core/vm/evm_test.go @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright 2025 The go-stablenet Authors +// This file is part of the go-stablenet library. +// +// The go-stablenet library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-stablenet library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-stablenet library. If not, see . + +package vm + +import ( + "crypto/ecdsa" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +type account struct { + privKey *ecdsa.PrivateKey + address common.Address +} + +type testAccounts struct { + caller *account + target *account +} + +func buildAccounts(statedb StateDB, callerBlacklisted, targetBlacklisted bool) testAccounts { + var accounts testAccounts + + accounts.caller = newAccount(statedb) + if callerBlacklisted { + statedb.SetBlacklisted(accounts.caller.address) + } + accounts.target = newAccount(statedb) + if targetBlacklisted { + statedb.SetBlacklisted(accounts.target.address) + } + + return accounts +} + +func newAccount(statedb StateDB) *account { + key, _ := crypto.GenerateKey() + address := crypto.PubkeyToAddress(key.PublicKey) + + account := &account{key, address} + statedb.CreateAccount(account.address) + statedb.AddBalance(account.address, uint256.NewInt(0)) + + return account +} + +func newStateDB() *state.StateDB { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + return statedb +} + +func newTestEvm(statedb StateDB) *EVM { + vmctx := BlockContext{ + Transfer: func(StateDB, common.Address, common.Address, *uint256.Int) {}, + CanTransfer: func(sd StateDB, a common.Address, i *uint256.Int) bool { return true }, + } + return NewEVM(vmctx, TxContext{}, statedb, params.TestWBFTChainConfig, Config{}) +} + +func TestBlacklistedAccountTx(t *testing.T) { + t.Run("Call", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + callerBlacklisted bool + targetBlacklisted bool + expectErr bool + getErrPart func(accts testAccounts) string + }{ + { + name: "unrelated to any blacklisted account", + callerBlacklisted: false, + targetBlacklisted: false, + expectErr: false, + }, + { + name: "caller is blacklisted", + callerBlacklisted: true, + targetBlacklisted: false, + expectErr: true, + getErrPart: func(accts testAccounts) string { + return fmt.Sprintf("caller %s", accts.caller.address.Hex()) + }, + }, + { + name: "target is blacklisted", + callerBlacklisted: false, + targetBlacklisted: true, + expectErr: true, + getErrPart: func(accts testAccounts) string { + return fmt.Sprintf("target %s", accts.target.address.Hex()) + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + statedb := newStateDB() + evm := newTestEvm(statedb) + accts := buildAccounts(statedb, tc.callerBlacklisted, tc.targetBlacklisted) + + caller := accts.caller + target := accts.target + + callerRef := AccountRef(caller.address) + + _, _, err := evm.Call(callerRef, target.address, []byte{}, 0, uint256.NewInt(0)) + if tc.expectErr { + require.ErrorIs(t, err, ErrBlacklistedAccount) + require.Contains(t, err.Error(), tc.getErrPart(accts)) + } else { + require.NoError(t, err) + } + }) + } + }) + + t.Run("Create", func(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + callerBlacklisted bool + expectErr bool + getErrPart func(accts testAccounts) string + }{ + { + name: "unrelated to any blacklisted account", + callerBlacklisted: false, + expectErr: false, + }, + { + name: "caller is blacklisted", + callerBlacklisted: true, + expectErr: true, + getErrPart: func(accts testAccounts) string { + return fmt.Sprintf("caller %s", accts.caller.address.Hex()) + }, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + statedb := newStateDB() + evm := newTestEvm(statedb) + accts := buildAccounts(statedb, tc.callerBlacklisted, false) + + caller := accts.caller + callerRef := AccountRef(caller.address) + + constructorCode := []byte{0x00} + codeAndHash := codeAndHash{ + code: constructorCode, + } + + _, _, _, err := evm.create(callerRef, &codeAndHash, 0, uint256.NewInt(0), common.Address{}, CREATE) + if tc.expectErr { + require.ErrorIs(t, err, ErrBlacklistedAccount) + require.Contains(t, err.Error(), tc.getErrPart(accts)) + } else { + require.NoError(t, err) + } + }) + } + }) +} From d5a16ba30d5ad58cbbe4f4c95d48b1475270c4a3 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Fri, 14 Nov 2025 15:09:52 +0900 Subject: [PATCH 11/30] test: simplify account setup by applying blacklist after creation --- core/txpool/validation_test.go | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go index c5b7ffec2..628f2b5c9 100644 --- a/core/txpool/validation_test.go +++ b/core/txpool/validation_test.go @@ -91,13 +91,6 @@ func newAccount(statedb *state.StateDB) *account { return account } -func newBlacklistedAccount(statedb *state.StateDB) *account { - account := newAccount(statedb) - statedb.SetBlacklisted(account.address) - - return account -} - func newDynamicFeeTx(to *common.Address) types.DynamicFeeTx { return types.DynamicFeeTx{ ChainID: chainConfig.ChainID, @@ -145,22 +138,17 @@ func signFeeDelegateTx(rawTx *types.Transaction, feePayer *account) (*types.Tran func buildAccounts(statedb *state.StateDB, senderBlacklisted, recipientBlacklisted, feePayerBlacklisted bool) testAccounts { var accounts testAccounts + accounts.sender = newAccount(statedb) if senderBlacklisted { - accounts.sender = newBlacklistedAccount(statedb) - } else { - accounts.sender = newAccount(statedb) + statedb.SetBlacklisted(accounts.sender.address) } - + accounts.recipient = newAccount(statedb) if recipientBlacklisted { - accounts.recipient = newBlacklistedAccount(statedb) - } else { - accounts.recipient = newAccount(statedb) + statedb.SetBlacklisted(accounts.recipient.address) } - + accounts.feePayer = newAccount(statedb) if feePayerBlacklisted { - accounts.feePayer = newBlacklistedAccount(statedb) - } else { - accounts.feePayer = newAccount(statedb) + statedb.SetBlacklisted(accounts.feePayer.address) } return accounts From 1aa0a00bc4890e5840f012429f0e24e879400364 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Fri, 14 Nov 2025 17:47:57 +0900 Subject: [PATCH 12/30] test: add selfdestruct validation tests for blacklisted accounts --- core/vm/instructions_test.go | 103 +++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 8653864d1..210b2fc09 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" + "github.com/stretchr/testify/require" ) type TwoOperandTestcase struct { @@ -928,3 +929,105 @@ func TestOpMCopy(t *testing.T) { } } } + +func TestSelfdestructBlacklistedAccount(t *testing.T) { + contractAddr := common.HexToAddress("0x1000000000000000000000000000000000000001") + beneficiaryAddr := common.HexToAddress("0x2000000000000000000000000000000000000002") + + tests := []struct { + name string + contractBlacklisted bool + beneficiaryBlacklisted bool + instruction func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) + expectErr bool + errPart string + }{ + { + name: "opSelfdestruct unrelated to any blacklisted account", + contractBlacklisted: false, + beneficiaryBlacklisted: false, + instruction: opSelfdestruct, + expectErr: false, + }, + { + name: "opSelfdestruct contract is blacklisted", + contractBlacklisted: true, + beneficiaryBlacklisted: false, + instruction: opSelfdestruct, + expectErr: true, + errPart: fmt.Sprintf("contract %s", contractAddr.Hex()), + }, + { + name: "opSelfdestruct beneficiary is blacklisted", + contractBlacklisted: false, + beneficiaryBlacklisted: true, + instruction: opSelfdestruct, + expectErr: true, + errPart: fmt.Sprintf("beneficiary %s", beneficiaryAddr.Hex()), + }, + { + name: "opSelfdestruct6780 unrelated to any blacklisted account", + contractBlacklisted: false, + beneficiaryBlacklisted: false, + instruction: opSelfdestruct6780, + expectErr: false, + }, + { + name: "opSelfdestruct6780 contract is blacklisted", + contractBlacklisted: true, + beneficiaryBlacklisted: false, + instruction: opSelfdestruct6780, + expectErr: true, + errPart: fmt.Sprintf("contract %s", contractAddr.Hex()), + }, + { + name: "opSelfdestruct6780 beneficiary is blacklisted", + contractBlacklisted: false, + beneficiaryBlacklisted: true, + instruction: opSelfdestruct6780, + expectErr: true, + errPart: fmt.Sprintf("beneficiary %s", beneficiaryAddr.Hex()), + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + var ( + statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestWBFTChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + + statedb.CreateAccount(contractAddr) + if tc.contractBlacklisted { + statedb.SetBlacklisted(contractAddr) + } + statedb.CreateAccount(beneficiaryAddr) + if tc.beneficiaryBlacklisted { + statedb.SetBlacklisted(beneficiaryAddr) + } + + stack.push(new(uint256.Int).SetBytes(beneficiaryAddr.Bytes())) + contract := Contract{ + self: contractRef{addr: contractAddr}, + } + _, err := tc.instruction(&pc, evmInterpreter, &ScopeContext{mem, stack, &contract}) + if err == errStopToken { + err = nil + } + + if tc.expectErr { + require.ErrorIs(t, err, ErrBlacklistedAccount) + require.Contains(t, err.Error(), tc.errPart) + } else { + require.NoError(t, err) + } + }) + } +} From 8b128a0ed585048e7a2cb21fbb1c7f21d9879786 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Fri, 14 Nov 2025 18:25:24 +0900 Subject: [PATCH 13/30] feat: enforce blacklisted fee payer validation in StateTransition --- core/state_transition.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/state_transition.go b/core/state_transition.go index 3e71e4960..c9b38102e 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -533,6 +533,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // fee delegation if st.msg.FeePayer != nil { payer = *st.msg.FeePayer + if rules.IsAnzeon && st.state.IsBlacklisted(payer) { + return nil, fmt.Errorf("%w: fee payer %s", ErrBlacklistedAccount, payer.Hex()) + } } st.evm.AddTransferLog(payer, st.evm.Context.Coinbase, fee) } From f15c1f93f0a17d83d870b628e61720ed819e8b6f Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Mon, 17 Nov 2025 11:27:55 +0900 Subject: [PATCH 14/30] refactor: simplify StateAt error handling in signer verification --- consensus/wbft/common/errors.go | 3 --- consensus/wbft/engine/engine.go | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/consensus/wbft/common/errors.go b/consensus/wbft/common/errors.go index 5f13cc818..8153f340f 100644 --- a/consensus/wbft/common/errors.go +++ b/consensus/wbft/common/errors.go @@ -144,9 +144,6 @@ var ( ErrEpochInfoIsNotNil = errors.New("epoch info should be nil for non-epoch block") - // ErrStateNotFound is returned when the state for the given root is missing. - ErrStateNotFound = errors.New("state not found") - // ErrBlacklistedSigner is returned when a block is signed by a blacklisted account. ErrBlacklistedSigner = errors.New("signer is blacklisted") ) diff --git a/consensus/wbft/engine/engine.go b/consensus/wbft/engine/engine.go index ae5cc461e..68da0995a 100644 --- a/consensus/wbft/engine/engine.go +++ b/consensus/wbft/engine/engine.go @@ -370,7 +370,7 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H state, err := chain.StateAt(parent.Root) if err != nil { - return fmt.Errorf("%w: parent %s", wbftcommon.ErrStateNotFound, parent.Hash().Hex()) + return err } if state.IsBlacklisted(signer) { return fmt.Errorf("%w: signer %s", wbftcommon.ErrBlacklistedSigner, signer.Hex()) From 2a64056d5136f07a87f7b678564b8da7826ab6c9 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Mon, 17 Nov 2025 11:31:11 +0900 Subject: [PATCH 15/30] test: add signer validation tests for blacklisted accounts --- consensus/wbft/engine/engine_test.go | 102 ++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/consensus/wbft/engine/engine_test.go b/consensus/wbft/engine/engine_test.go index c40b6f8fe..198fa35f9 100644 --- a/consensus/wbft/engine/engine_test.go +++ b/consensus/wbft/engine/engine_test.go @@ -23,6 +23,7 @@ import ( "bytes" "crypto/ecdsa" "errors" + "fmt" "math/big" "reflect" "slices" @@ -36,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/wbft" wbftcommon "github.com/ethereum/go-ethereum/consensus/wbft/common" wbftcore "github.com/ethereum/go-ethereum/consensus/wbft/core" + "github.com/ethereum/go-ethereum/consensus/wbft/validator" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -45,6 +47,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/systemcontracts" + "github.com/stretchr/testify/require" ) // RLP data can be generated by using TestGeneratingGenesisExtra in testutils package @@ -442,6 +445,7 @@ func TestWriteRandao(t *testing.T) { type fakeChain struct { chainConfig *params.ChainConfig headers []*types.Header + statedb *state.StateDB } var _ consensus.ChainHeaderReader = (*fakeChain)(nil) @@ -456,7 +460,7 @@ func (f *fakeChain) GetHeader(hash common.Hash, number uint64) *types.Header { func (f *fakeChain) GetHeaderByNumber(number uint64) *types.Header { return nil } func (f *fakeChain) GetHeaderByHash(hash common.Hash) *types.Header { return nil } func (f *fakeChain) GetTd(hash common.Hash, number uint64) *big.Int { return nil } -func (f *fakeChain) StateAt(root common.Hash) (*state.StateDB, error) { return nil, nil } +func (f *fakeChain) StateAt(root common.Hash) (*state.StateDB, error) { return f.statedb, nil } func (f *fakeChain) insertHeader(h *types.Header) { f.headers = append(f.headers, h) @@ -882,3 +886,99 @@ func TestComputeShuffledIndex(t *testing.T) { t.Errorf("expected results to be equal, but got different results: %v and %v", result1, result2) } } + +func TestBlacklistedSigner(t *testing.T) { + signers := newAccounts(2) + signer := signers[0] + blacklistedSigner := signers[1] + + tests := []struct { + name string + signer account + parentExists bool + blacklisted bool + expectErr error + errContainsPart string + }{ + { + name: "missing parent header", + signer: signer, + parentExists: false, + blacklisted: false, + expectErr: consensus.ErrUnknownAncestor, + }, + { + name: "not blacklisted signer", + signer: signer, + parentExists: true, + blacklisted: false, + expectErr: nil, + }, + { + name: "blacklisted signer", + signer: blacklistedSigner, + parentExists: true, + blacklisted: true, + expectErr: wbftcommon.ErrBlacklistedSigner, + errContainsPart: fmt.Sprintf("signer %s", blacklistedSigner.addr.Hex()), + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + var parent *types.Header + if tc.parentExists { + parent = &types.Header{ + Number: big.NewInt(1), + Root: common.Hash{}, + } + } + + mockState, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + mockState.CreateAccount(tc.signer.addr) + if tc.blacklisted { + mockState.SetBlacklisted(tc.signer.addr) + } + + fc := &fakeChain{ + chainConfig: params.TestWBFTChainConfig, + headers: []*types.Header{nil, parent}, + statedb: mockState, + } + + engine := NewEngine(nil, common.Address{}, nil, nil) + + header := &types.Header{ + Number: big.NewInt(2), + ParentHash: common.HexToHash("0x1"), + Coinbase: tc.signer.addr, + } + + var parents []*types.Header + if tc.parentExists { + header.ParentHash = parent.Hash() + parents = []*types.Header{parent} + } + + addrs := []common.Address{ + tc.signer.addr, + } + blsPubKeys := [][]byte{ + tc.signer.blsKey.PublicKey().Marshal(), + } + validators := validator.NewSet(addrs, blsPubKeys, wbft.NewRoundRobinProposerPolicy()) + + err := engine.verifySigner(fc, header, parents, validators) + + if tc.expectErr == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expectErr) + if tc.errContainsPart != "" { + require.Contains(t, err.Error(), tc.errContainsPart) + } + } + }) + } +} From 9ce07007d0a54fda4673cbd8a372a4ec24a2846d Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Mon, 17 Nov 2025 12:45:50 +0900 Subject: [PATCH 16/30] test: simplify blacklisted signer test --- consensus/wbft/engine/engine_test.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/consensus/wbft/engine/engine_test.go b/consensus/wbft/engine/engine_test.go index 198fa35f9..92c6cf1a7 100644 --- a/consensus/wbft/engine/engine_test.go +++ b/consensus/wbft/engine/engine_test.go @@ -888,13 +888,10 @@ func TestComputeShuffledIndex(t *testing.T) { } func TestBlacklistedSigner(t *testing.T) { - signers := newAccounts(2) - signer := signers[0] - blacklistedSigner := signers[1] + signer := testAccount1 tests := []struct { name string - signer account parentExists bool blacklisted bool expectErr error @@ -902,25 +899,22 @@ func TestBlacklistedSigner(t *testing.T) { }{ { name: "missing parent header", - signer: signer, parentExists: false, blacklisted: false, expectErr: consensus.ErrUnknownAncestor, }, { name: "not blacklisted signer", - signer: signer, parentExists: true, blacklisted: false, expectErr: nil, }, { name: "blacklisted signer", - signer: blacklistedSigner, parentExists: true, blacklisted: true, expectErr: wbftcommon.ErrBlacklistedSigner, - errContainsPart: fmt.Sprintf("signer %s", blacklistedSigner.addr.Hex()), + errContainsPart: fmt.Sprintf("signer %s", signer.addr.Hex()), }, } @@ -936,9 +930,9 @@ func TestBlacklistedSigner(t *testing.T) { } mockState, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - mockState.CreateAccount(tc.signer.addr) + mockState.CreateAccount(signer.addr) if tc.blacklisted { - mockState.SetBlacklisted(tc.signer.addr) + mockState.SetBlacklisted(signer.addr) } fc := &fakeChain{ @@ -952,7 +946,7 @@ func TestBlacklistedSigner(t *testing.T) { header := &types.Header{ Number: big.NewInt(2), ParentHash: common.HexToHash("0x1"), - Coinbase: tc.signer.addr, + Coinbase: signer.addr, } var parents []*types.Header @@ -962,10 +956,10 @@ func TestBlacklistedSigner(t *testing.T) { } addrs := []common.Address{ - tc.signer.addr, + signer.addr, } blsPubKeys := [][]byte{ - tc.signer.blsKey.PublicKey().Marshal(), + signer.blsKey.PublicKey().Marshal(), } validators := validator.NewSet(addrs, blsPubKeys, wbft.NewRoundRobinProposerPolicy()) From 421dc60ed2dd89ad00db2d872c2b993fb933a81e Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Mon, 17 Nov 2025 13:07:54 +0900 Subject: [PATCH 17/30] refactor: use core ErrBlacklistedAccount in txpool --- core/txpool/errors.go | 4 ---- core/txpool/validation.go | 6 +++--- core/txpool/validation_test.go | 5 +++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/core/txpool/errors.go b/core/txpool/errors.go index d0976e238..55bfe9e9e 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -72,8 +72,4 @@ var ( // WEMIX ErrSenderInsufficientFunds is returned if the value cost of executing a transaction // is higher than the balance of the sender's account. ErrSenderInsufficientFunds = errors.New("fee delegation: insufficient sender's funds for value") - - // ErrBlacklistedAccount is returned if a transaction involves an account - // that is blacklisted. - ErrBlacklistedAccount = errors.New("account is blacklisted") ) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index dbbad4a35..2b5c9c7db 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -225,10 +225,10 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op // Ensure that neither the sender nor the recipient is blacklisted if opts.Config.AnzeonEnabled() { if opts.State.IsBlacklisted(from) { - return fmt.Errorf("%w: from %s", ErrBlacklistedAccount, from.Hex()) + return fmt.Errorf("%w: from %s", core.ErrBlacklistedAccount, from.Hex()) } if to := tx.To(); to != nil && opts.State.IsBlacklisted(*to) { - return fmt.Errorf("%w: to %s", ErrBlacklistedAccount, to.Hex()) + return fmt.Errorf("%w: to %s", core.ErrBlacklistedAccount, to.Hex()) } } next := opts.State.GetNonce(from) @@ -247,7 +247,7 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op feePayer := tx.FeePayer() // Ensure that the fee payer is not blacklisted if opts.Config.AnzeonEnabled() && opts.State.IsBlacklisted(*feePayer) { - return fmt.Errorf("%w: fee payer %s", ErrBlacklistedAccount, feePayer.Hex()) + return fmt.Errorf("%w: fee payer %s", core.ErrBlacklistedAccount, feePayer.Hex()) } if opts.State.GetBalance(from).ToBig().Cmp(tx.Value()) < 0 { return ErrSenderInsufficientFunds diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go index 628f2b5c9..36fef2bc1 100644 --- a/core/txpool/validation_test.go +++ b/core/txpool/validation_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -206,7 +207,7 @@ func TestBlacklistedAccountTx(t *testing.T) { err := ValidateTransactionWithState(signedTx, signer, opts) if tc.expectErr { - require.ErrorIs(t, err, ErrBlacklistedAccount) + require.ErrorIs(t, err, core.ErrBlacklistedAccount) require.Contains(t, err.Error(), tc.getErrPart(accts)) } else { require.NoError(t, err) @@ -282,7 +283,7 @@ func TestBlacklistedAccountTx(t *testing.T) { err := ValidateTransactionWithState(feePayerSignedTx, signer, opts) if tc.expectErr { - require.ErrorIs(t, err, ErrBlacklistedAccount) + require.ErrorIs(t, err, core.ErrBlacklistedAccount) require.Contains(t, err.Error(), tc.getErrPart(accts)) } else { require.NoError(t, err) From 646031a253d1696844020289d1de5a241cc052cb Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Mon, 17 Nov 2025 19:11:14 +0900 Subject: [PATCH 18/30] fix: handle missing parent state in verifySigner by skipping blacklist validation --- consensus/wbft/common/errors.go | 2 ++ consensus/wbft/engine/engine.go | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/consensus/wbft/common/errors.go b/consensus/wbft/common/errors.go index 8153f340f..0b146b9dd 100644 --- a/consensus/wbft/common/errors.go +++ b/consensus/wbft/common/errors.go @@ -144,6 +144,8 @@ var ( ErrEpochInfoIsNotNil = errors.New("epoch info should be nil for non-epoch block") + ErrStateUnavailable = errors.New("state unavailable for verification") + // ErrBlacklistedSigner is returned when a block is signed by a blacklisted account. ErrBlacklistedSigner = errors.New("signer is blacklisted") ) diff --git a/consensus/wbft/engine/engine.go b/consensus/wbft/engine/engine.go index 68da0995a..47655f057 100644 --- a/consensus/wbft/engine/engine.go +++ b/consensus/wbft/engine/engine.go @@ -288,7 +288,12 @@ func (e *Engine) verifyCascadingFields(chain consensus.ChainHeaderReader, header // Verify signer if err := e.verifySigner(chain, header, parents, validators); err != nil { - return err + if !errors.Is(err, wbftcommon.ErrStateUnavailable) { + return err + } + // Skip blacklisted signer verification when state is not yet available. + // For more details, refer to the comment in verifyGasTip. + log.Trace("WBFT: Skipping blacklisted signer verification due to unavailable state", "number", header.Number, "err", err) } // extract the extra data from the header @@ -356,6 +361,11 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H return err } + // Signer should be in the validator set of previous block's extraData. + if _, v := validators.GetByAddress(signer); v == nil { + return wbftcommon.ErrUnauthorized + } + // Check parent var parent *types.Header if len(parents) > 0 { @@ -370,17 +380,12 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H state, err := chain.StateAt(parent.Root) if err != nil { - return err + return fmt.Errorf("%w: %v", wbftcommon.ErrStateUnavailable, err) } if state.IsBlacklisted(signer) { return fmt.Errorf("%w: signer %s", wbftcommon.ErrBlacklistedSigner, signer.Hex()) } - // Signer should be in the validator set of previous block's extraData. - if _, v := validators.GetByAddress(signer); v == nil { - return wbftcommon.ErrUnauthorized - } - return nil } From 6edc12d32b9354616c24f1ed289326fbb3002f5a Mon Sep 17 00:00:00 2001 From: eomti-wm Date: Tue, 18 Nov 2025 10:42:49 +0900 Subject: [PATCH 19/30] fix: add native managers to access list --- core/state/statedb.go | 11 ++++++----- core/state_transition.go | 2 +- core/vm/interface.go | 2 +- core/vm/runtime/runtime.go | 6 +++--- tests/state_test.go | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 27e83682a..13dbb0307 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -1358,11 +1358,14 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er // - Add precompiles to access list (2929) // - Add the contents of the optional tx access list (2930) // +// Anzeon fork: +// - Add native managers to access list +// // Potential EIPs: // - Reset access list (Berlin) // - Add coinbase to access list (EIP-3651) // - Reset transient storage (EIP-1153) -func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { +func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles, nativeManagers []common.Address, list types.AccessList) { if rules.IsBerlin { // Clear out any leftover from previous executions al := newAccessList() @@ -1385,10 +1388,8 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d if rules.IsShanghai { // EIP-3651: warm coinbase al.AddAddress(coinbase) } - - if rules.IsAnzeon { - al.AddAddress(params.NativeCoinManagerAddress) - al.AddAddress(params.AccountManagerAddress) + for _, addr := range nativeManagers { + al.AddAddress(addr) } } // Reset transient storage at the beginning of transaction execution diff --git a/core/state_transition.go b/core/state_transition.go index c9b38102e..17c28239a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -490,7 +490,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), msg.AccessList) var ( ret []byte diff --git a/core/vm/interface.go b/core/vm/interface.go index 64fcac5ee..52bb163af 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -72,7 +72,7 @@ type StateDB interface { // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // even if the feature/fork is not active yet AddSlotToAccessList(addr common.Address, slot common.Hash) - Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles, nativeManagers []common.Address, txAccesses types.AccessList) RevertToSnapshot(int) Snapshot() int diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 3513ed3a3..f6eb812a0 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -127,7 +127,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), nil) cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code) @@ -160,7 +160,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), nil) // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( sender, @@ -188,7 +188,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), nil) // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( diff --git a/tests/state_test.go b/tests/state_test.go index 1d749d8bc..9281f89ae 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -301,7 +301,7 @@ func runBenchmark(b *testing.B, t *StateTest) { b.ResetTimer() for n := 0; n < b.N; n++ { snapshot := state.StateDB.Snapshot() - state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), vm.ActiveNativeManagers(rules), msg.AccessList) b.StartTimer() start := time.Now() From 3e08c268d627b3363bb2285bc6b608218c794013 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Tue, 18 Nov 2025 11:10:21 +0900 Subject: [PATCH 20/30] refactor: switch to structured ErrBlacklistedAccount and update tests --- consensus/wbft/common/errors.go | 2 +- consensus/wbft/engine/engine.go | 2 +- consensus/wbft/engine/engine_test.go | 3 +- core/error.go | 22 +++- core/state_transition.go | 6 +- core/txpool/validation.go | 6 +- core/txpool/validation_test.go | 157 +++++++++++---------------- core/vm/errors.go | 23 +++- core/vm/evm.go | 7 +- core/vm/evm_test.go | 123 ++++++++++----------- core/vm/instructions.go | 9 +- core/vm/instructions_test.go | 96 ++++++++-------- 12 files changed, 217 insertions(+), 239 deletions(-) diff --git a/consensus/wbft/common/errors.go b/consensus/wbft/common/errors.go index 0b146b9dd..a709efc0f 100644 --- a/consensus/wbft/common/errors.go +++ b/consensus/wbft/common/errors.go @@ -147,5 +147,5 @@ var ( ErrStateUnavailable = errors.New("state unavailable for verification") // ErrBlacklistedSigner is returned when a block is signed by a blacklisted account. - ErrBlacklistedSigner = errors.New("signer is blacklisted") + ErrBlacklistedSigner = errors.New("blacklisted signer") ) diff --git a/consensus/wbft/engine/engine.go b/consensus/wbft/engine/engine.go index 47655f057..f851ce584 100644 --- a/consensus/wbft/engine/engine.go +++ b/consensus/wbft/engine/engine.go @@ -383,7 +383,7 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H return fmt.Errorf("%w: %v", wbftcommon.ErrStateUnavailable, err) } if state.IsBlacklisted(signer) { - return fmt.Errorf("%w: signer %s", wbftcommon.ErrBlacklistedSigner, signer.Hex()) + return fmt.Errorf("%w: %s", wbftcommon.ErrBlacklistedSigner, signer.Hex()) } return nil diff --git a/consensus/wbft/engine/engine_test.go b/consensus/wbft/engine/engine_test.go index 92c6cf1a7..bbfa6542a 100644 --- a/consensus/wbft/engine/engine_test.go +++ b/consensus/wbft/engine/engine_test.go @@ -23,7 +23,6 @@ import ( "bytes" "crypto/ecdsa" "errors" - "fmt" "math/big" "reflect" "slices" @@ -914,7 +913,7 @@ func TestBlacklistedSigner(t *testing.T) { parentExists: true, blacklisted: true, expectErr: wbftcommon.ErrBlacklistedSigner, - errContainsPart: fmt.Sprintf("signer %s", signer.addr.Hex()), + errContainsPart: signer.addr.Hex(), }, } diff --git a/core/error.go b/core/error.go index ed326f63d..7deb7054d 100644 --- a/core/error.go +++ b/core/error.go @@ -18,7 +18,9 @@ package core import ( "errors" + "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -101,9 +103,6 @@ var ( // ErrSenderNoEOA is returned if the sender of a transaction is a contract. ErrSenderNoEOA = errors.New("sender not an eoa") - // ErrBlacklistedAccount is returned if the sender or recipient is blacklisted. - ErrBlacklistedAccount = errors.New("account is blacklisted") - // ErrBlobFeeCapTooLow is returned if the transaction fee cap is less than the // blob gas fee of the block. ErrBlobFeeCapTooLow = errors.New("max fee per blob gas less than block blob gas fee") @@ -114,3 +113,20 @@ var ( // ErrBlobTxCreate is returned if a blob transaction has no explicit to field. ErrBlobTxCreate = errors.New("blob transaction of type create") ) + +type BlacklistRole string + +const ( + SenderRole BlacklistRole = "sender" + RecipientRole BlacklistRole = "recipient" + FeePayerRole BlacklistRole = "feePayer" +) + +type ErrBlacklistedAccount struct { + Address common.Address + Role BlacklistRole +} + +func (e *ErrBlacklistedAccount) Error() string { + return fmt.Sprintf("blacklisted %s: %s", string(e.Role), e.Address.Hex()) +} diff --git a/core/state_transition.go b/core/state_transition.go index 17c28239a..852fe55bc 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -478,12 +478,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if rules.IsAnzeon { // Check sender blacklist if st.state.IsBlacklisted(msg.From) { - return nil, fmt.Errorf("%w: sender %v", ErrBlacklistedAccount, msg.From.Hex()) + return nil, &ErrBlacklistedAccount{Address: msg.From, Role: SenderRole} } // Check recipient blacklist (only for transfers, not for contract creation) if msg.To != nil && st.state.IsBlacklisted(*msg.To) { - return nil, fmt.Errorf("%w: recipient %v", ErrBlacklistedAccount, msg.To.Hex()) + return nil, &ErrBlacklistedAccount{Address: *msg.To, Role: RecipientRole} } } @@ -534,7 +534,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.msg.FeePayer != nil { payer = *st.msg.FeePayer if rules.IsAnzeon && st.state.IsBlacklisted(payer) { - return nil, fmt.Errorf("%w: fee payer %s", ErrBlacklistedAccount, payer.Hex()) + return nil, &ErrBlacklistedAccount{Address: payer, Role: FeePayerRole} } } st.evm.AddTransferLog(payer, st.evm.Context.Coinbase, fee) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 2b5c9c7db..239dfbe2d 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -225,10 +225,10 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op // Ensure that neither the sender nor the recipient is blacklisted if opts.Config.AnzeonEnabled() { if opts.State.IsBlacklisted(from) { - return fmt.Errorf("%w: from %s", core.ErrBlacklistedAccount, from.Hex()) + return &core.ErrBlacklistedAccount{Address: from, Role: core.SenderRole} } if to := tx.To(); to != nil && opts.State.IsBlacklisted(*to) { - return fmt.Errorf("%w: to %s", core.ErrBlacklistedAccount, to.Hex()) + return &core.ErrBlacklistedAccount{Address: *to, Role: core.RecipientRole} } } next := opts.State.GetNonce(from) @@ -247,7 +247,7 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op feePayer := tx.FeePayer() // Ensure that the fee payer is not blacklisted if opts.Config.AnzeonEnabled() && opts.State.IsBlacklisted(*feePayer) { - return fmt.Errorf("%w: fee payer %s", core.ErrBlacklistedAccount, feePayer.Hex()) + return &core.ErrBlacklistedAccount{Address: *feePayer, Role: core.FeePayerRole} } if opts.State.GetBalance(from).ToBig().Cmp(tx.Value()) < 0 { return ErrSenderInsufficientFunds diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go index 36fef2bc1..cbc47b476 100644 --- a/core/txpool/validation_test.go +++ b/core/txpool/validation_test.go @@ -19,7 +19,6 @@ package txpool import ( "crypto/ecdsa" - "fmt" "math/big" "testing" @@ -39,12 +38,6 @@ type account struct { address common.Address } -type testAccounts struct { - sender *account - recipient *account - feePayer *account -} - var ( chainConfig *params.ChainConfig signer types.Signer @@ -136,57 +129,26 @@ func signFeeDelegateTx(rawTx *types.Transaction, feePayer *account) (*types.Tran return types.SignTx(tx, feeDelegateSigner, feePayer.privKey) } -func buildAccounts(statedb *state.StateDB, senderBlacklisted, recipientBlacklisted, feePayerBlacklisted bool) testAccounts { - var accounts testAccounts - - accounts.sender = newAccount(statedb) - if senderBlacklisted { - statedb.SetBlacklisted(accounts.sender.address) - } - accounts.recipient = newAccount(statedb) - if recipientBlacklisted { - statedb.SetBlacklisted(accounts.recipient.address) - } - accounts.feePayer = newAccount(statedb) - if feePayerBlacklisted { - statedb.SetBlacklisted(accounts.feePayer.address) - } - - return accounts -} - func TestBlacklistedAccountTx(t *testing.T) { t.Run("DynamicFeeTx", func(t *testing.T) { tests := []struct { - name string - senderBlacklisted bool - recipientBlacklisted bool - expectErr bool - getErrPart func(accts testAccounts) string + name string + blacklistedRole core.BlacklistRole + expectErr bool }{ { - name: "unrelated to any blacklisted account", - senderBlacklisted: false, - recipientBlacklisted: false, - expectErr: false, + name: "unrelated to any blacklisted account", + expectErr: false, }, { - name: "sender is blacklisted", - senderBlacklisted: true, - recipientBlacklisted: false, - expectErr: true, - getErrPart: func(accts testAccounts) string { - return fmt.Sprintf("from %s", accts.sender.address.Hex()) - }, + name: "sender is blacklisted", + blacklistedRole: core.SenderRole, + expectErr: true, }, { - name: "recipient is blacklisted", - senderBlacklisted: false, - recipientBlacklisted: true, - expectErr: true, - getErrPart: func(accts testAccounts) string { - return fmt.Sprintf("to %s", accts.recipient.address.Hex()) - }, + name: "recipient is blacklisted", + blacklistedRole: core.RecipientRole, + expectErr: true, }, } @@ -196,19 +158,31 @@ func TestBlacklistedAccountTx(t *testing.T) { t.Parallel() statedb, opts := newTestState() + testAccts := map[core.BlacklistRole]*account{ + core.SenderRole: newAccount(statedb), + core.RecipientRole: newAccount(statedb), + } - accts := buildAccounts(statedb, tc.senderBlacklisted, tc.recipientBlacklisted, false) + blacklistedAcct, ok := testAccts[tc.blacklistedRole] + if ok { + statedb.SetBlacklisted(blacklistedAcct.address) + } - sender := accts.sender - recipient := accts.recipient + sender := testAccts[core.SenderRole] + recipient := testAccts[core.RecipientRole] tx := newDynamicFeeTx(&recipient.address) signedTx, _ := signDynamicFeeTx(tx, sender) err := ValidateTransactionWithState(signedTx, signer, opts) + if tc.expectErr { - require.ErrorIs(t, err, core.ErrBlacklistedAccount) - require.Contains(t, err.Error(), tc.getErrPart(accts)) + require.Error(t, err) + + var haveErr *core.ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + require.Equal(t, tc.blacklistedRole, haveErr.Role) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) } @@ -218,49 +192,28 @@ func TestBlacklistedAccountTx(t *testing.T) { t.Run("FeeDelegateDynamicFeeTx", func(t *testing.T) { tests := []struct { - name string - senderBlacklisted bool - recipientBlacklisted bool - feePayerBlacklisted bool - expectErr bool - getErrPart func(accts testAccounts) string + name string + blacklistedRole core.BlacklistRole + expectErr bool }{ { - name: "unrelated to any blacklisted account", - senderBlacklisted: false, - recipientBlacklisted: false, - feePayerBlacklisted: false, - expectErr: false, + name: "unrelated to any blacklisted account", + expectErr: false, }, { - name: "sender is blacklisted", - senderBlacklisted: true, - recipientBlacklisted: false, - feePayerBlacklisted: false, - expectErr: true, - getErrPart: func(accts testAccounts) string { - return fmt.Sprintf("from %s", accts.sender.address.Hex()) - }, + name: "sender is blacklisted", + blacklistedRole: core.SenderRole, + expectErr: true, }, { - name: "recipient is blacklisted", - senderBlacklisted: false, - recipientBlacklisted: true, - feePayerBlacklisted: false, - expectErr: true, - getErrPart: func(accts testAccounts) string { - return fmt.Sprintf("to %s", accts.recipient.address.Hex()) - }, + name: "recipient is blacklisted", + blacklistedRole: core.RecipientRole, + expectErr: true, }, { - name: "fee payer is blacklisted", - senderBlacklisted: false, - recipientBlacklisted: false, - feePayerBlacklisted: true, - expectErr: true, - getErrPart: func(accts testAccounts) string { - return fmt.Sprintf("fee payer %s", accts.feePayer.address.Hex()) - }, + name: "feePayer is blacklisted", + blacklistedRole: core.FeePayerRole, + expectErr: true, }, } @@ -270,21 +223,35 @@ func TestBlacklistedAccountTx(t *testing.T) { t.Parallel() statedb, opts := newTestState() + testAccts := map[core.BlacklistRole]*account{ + core.SenderRole: newAccount(statedb), + core.RecipientRole: newAccount(statedb), + core.FeePayerRole: newAccount(statedb), + } - accts := buildAccounts(statedb, tc.senderBlacklisted, tc.recipientBlacklisted, tc.feePayerBlacklisted) + blacklistedAcct, ok := testAccts[tc.blacklistedRole] + if ok { + statedb.SetBlacklisted(blacklistedAcct.address) + } - sender := accts.sender - recipient := accts.recipient - feePayer := accts.feePayer + sender := testAccts[core.SenderRole] + recipient := testAccts[core.RecipientRole] + feePayer := testAccts[core.FeePayerRole] tx := newDynamicFeeTx(&recipient.address) signedTx, _ := signDynamicFeeTx(tx, sender) feePayerSignedTx, _ := signFeeDelegateTx(signedTx, feePayer) err := ValidateTransactionWithState(feePayerSignedTx, signer, opts) + if tc.expectErr { - require.ErrorIs(t, err, core.ErrBlacklistedAccount) - require.Contains(t, err.Error(), tc.getErrPart(accts)) + require.Error(t, err) + + var haveErr *core.ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + + require.Equal(t, tc.blacklistedRole, haveErr.Role) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) } diff --git a/core/vm/errors.go b/core/vm/errors.go index af0fee782..7190401c8 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -19,6 +19,8 @@ package vm import ( "errors" "fmt" + + "github.com/ethereum/go-ethereum/common" ) // List evm execution errors @@ -47,9 +49,6 @@ var ( // errStopToken is an internal token indicating interpreter loop termination, // never returned to outside callers. errStopToken = errors.New("stop token") - - // ErrBlacklistedAccount indicates EVM execution with a blacklisted account - ErrBlacklistedAccount = errors.New("account is blacklisted") ) // ErrStackUnderflow wraps an evm error when the items on the stack less @@ -80,3 +79,21 @@ type ErrInvalidOpCode struct { } func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } + +type BlacklistRole string + +const ( + callerRole BlacklistRole = "caller" + targetRole BlacklistRole = "target" + contractRole BlacklistRole = "contract" + beneficiaryRole BlacklistRole = "beneficiary" +) + +type ErrBlacklistedAccount struct { + Address common.Address + Role BlacklistRole +} + +func (e *ErrBlacklistedAccount) Error() string { + return fmt.Sprintf("blacklisted %s: %s", string(e.Role), e.Address.Hex()) +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 401e5eb12..770b8af43 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,7 +17,6 @@ package vm import ( - "fmt" "math/big" "sync/atomic" @@ -199,10 +198,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // Fail if the caller or the target address is blacklisted under Anzeon rules if evm.chainRules.IsAnzeon { if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { - return nil, gas, fmt.Errorf("%w: caller %s", ErrBlacklistedAccount, callerAddr.Hex()) + return nil, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} } if evm.StateDB.IsBlacklisted(addr) { - return nil, gas, fmt.Errorf("%w: target %s", ErrBlacklistedAccount, addr.Hex()) + return nil, gas, &ErrBlacklistedAccount{Address: addr, Role: targetRole} } } m, isNativeManager := evm.nativeManager(addr) @@ -474,7 +473,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Fail if the caller is blacklisted under Anzeon rules if evm.chainRules.IsAnzeon && evm.StateDB.IsBlacklisted(caller.Address()) { - return nil, common.Address{}, gas, fmt.Errorf("%w: caller %s", ErrBlacklistedAccount, caller.Address().Hex()) + return nil, common.Address{}, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} } if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index eaf4ca25f..65c55fddd 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -19,7 +19,6 @@ package vm import ( "crypto/ecdsa" - "fmt" "testing" "github.com/ethereum/go-ethereum/common" @@ -37,26 +36,6 @@ type account struct { address common.Address } -type testAccounts struct { - caller *account - target *account -} - -func buildAccounts(statedb StateDB, callerBlacklisted, targetBlacklisted bool) testAccounts { - var accounts testAccounts - - accounts.caller = newAccount(statedb) - if callerBlacklisted { - statedb.SetBlacklisted(accounts.caller.address) - } - accounts.target = newAccount(statedb) - if targetBlacklisted { - statedb.SetBlacklisted(accounts.target.address) - } - - return accounts -} - func newAccount(statedb StateDB) *account { key, _ := crypto.GenerateKey() address := crypto.PubkeyToAddress(key.PublicKey) @@ -81,40 +60,28 @@ func newTestEvm(statedb StateDB) *EVM { return NewEVM(vmctx, TxContext{}, statedb, params.TestWBFTChainConfig, Config{}) } -func TestBlacklistedAccountTx(t *testing.T) { +func TestBlacklistedAccountExecution(t *testing.T) { t.Run("Call", func(t *testing.T) { t.Parallel() tests := []struct { - name string - callerBlacklisted bool - targetBlacklisted bool - expectErr bool - getErrPart func(accts testAccounts) string + name string + blacklistedRole BlacklistRole + expectErr bool }{ { - name: "unrelated to any blacklisted account", - callerBlacklisted: false, - targetBlacklisted: false, - expectErr: false, + name: "unrelated to any blacklisted account", + expectErr: false, }, { - name: "caller is blacklisted", - callerBlacklisted: true, - targetBlacklisted: false, - expectErr: true, - getErrPart: func(accts testAccounts) string { - return fmt.Sprintf("caller %s", accts.caller.address.Hex()) - }, + name: "caller is blacklisted", + blacklistedRole: callerRole, + expectErr: true, }, { - name: "target is blacklisted", - callerBlacklisted: false, - targetBlacklisted: true, - expectErr: true, - getErrPart: func(accts testAccounts) string { - return fmt.Sprintf("target %s", accts.target.address.Hex()) - }, + name: "target is blacklisted", + blacklistedRole: targetRole, + expectErr: true, }, } @@ -125,17 +92,31 @@ func TestBlacklistedAccountTx(t *testing.T) { statedb := newStateDB() evm := newTestEvm(statedb) - accts := buildAccounts(statedb, tc.callerBlacklisted, tc.targetBlacklisted) - caller := accts.caller - target := accts.target + testAccts := map[BlacklistRole]*account{ + callerRole: newAccount(statedb), + targetRole: newAccount(statedb), + } + + blacklistedAcct, ok := testAccts[tc.blacklistedRole] + if ok { + statedb.SetBlacklisted(blacklistedAcct.address) + } + + caller := testAccts[callerRole] + target := testAccts[targetRole] callerRef := AccountRef(caller.address) _, _, err := evm.Call(callerRef, target.address, []byte{}, 0, uint256.NewInt(0)) if tc.expectErr { - require.ErrorIs(t, err, ErrBlacklistedAccount) - require.Contains(t, err.Error(), tc.getErrPart(accts)) + require.Error(t, err) + + var haveErr *ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + + require.Equal(t, tc.blacklistedRole, haveErr.Role) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) } @@ -147,23 +128,18 @@ func TestBlacklistedAccountTx(t *testing.T) { t.Parallel() tests := []struct { - name string - callerBlacklisted bool - expectErr bool - getErrPart func(accts testAccounts) string + name string + blacklistedRole BlacklistRole + expectErr bool }{ { - name: "unrelated to any blacklisted account", - callerBlacklisted: false, - expectErr: false, + name: "unrelated to any blacklisted account", + expectErr: false, }, { - name: "caller is blacklisted", - callerBlacklisted: true, - expectErr: true, - getErrPart: func(accts testAccounts) string { - return fmt.Sprintf("caller %s", accts.caller.address.Hex()) - }, + name: "caller is blacklisted", + blacklistedRole: callerRole, + expectErr: true, }, } @@ -174,9 +150,17 @@ func TestBlacklistedAccountTx(t *testing.T) { statedb := newStateDB() evm := newTestEvm(statedb) - accts := buildAccounts(statedb, tc.callerBlacklisted, false) - caller := accts.caller + testAccts := map[BlacklistRole]*account{ + callerRole: newAccount(statedb), + } + + blacklistedAcct, ok := testAccts[tc.blacklistedRole] + if ok { + statedb.SetBlacklisted(blacklistedAcct.address) + } + + caller := testAccts[callerRole] callerRef := AccountRef(caller.address) constructorCode := []byte{0x00} @@ -186,8 +170,13 @@ func TestBlacklistedAccountTx(t *testing.T) { _, _, _, err := evm.create(callerRef, &codeAndHash, 0, uint256.NewInt(0), common.Address{}, CREATE) if tc.expectErr { - require.ErrorIs(t, err, ErrBlacklistedAccount) - require.Contains(t, err.Error(), tc.getErrPart(accts)) + require.Error(t, err) + + var haveErr *ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + + require.Equal(t, tc.blacklistedRole, haveErr.Role) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9d5b0ee60..f3144f28f 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,7 +17,6 @@ package vm import ( - "fmt" "math" "github.com/ethereum/go-ethereum/common" @@ -805,10 +804,10 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext // Deny SELFDESTRUCT if the contract or beneficiary is blacklisted under Anzeon rules if interpreter.evm.chainRules.IsAnzeon { if contractAddr := scope.Contract.Address(); interpreter.evm.StateDB.IsBlacklisted(contractAddr) { - return nil, fmt.Errorf("%w: contract %s", ErrBlacklistedAccount, contractAddr.Hex()) + return nil, &ErrBlacklistedAccount{Address: contractAddr, Role: contractRole} } if beneficiaryAddr := beneficiary.Bytes20(); interpreter.evm.StateDB.IsBlacklisted(beneficiaryAddr) { - return nil, fmt.Errorf("%w: beneficiary %s", ErrBlacklistedAccount, common.Address(beneficiaryAddr).Hex()) + return nil, &ErrBlacklistedAccount{Address: beneficiaryAddr, Role: beneficiaryRole} } } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) @@ -833,10 +832,10 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon // Deny SELFDESTRUCT if the contract or beneficiary is blacklisted under Anzeon rules if interpreter.evm.chainRules.IsAnzeon { if contractAddr := scope.Contract.Address(); interpreter.evm.StateDB.IsBlacklisted(contractAddr) { - return nil, fmt.Errorf("%w: contract %s", ErrBlacklistedAccount, contractAddr.Hex()) + return nil, &ErrBlacklistedAccount{Address: contractAddr, Role: contractRole} } if beneficiaryAddr := beneficiary.Bytes20(); interpreter.evm.StateDB.IsBlacklisted(beneficiaryAddr) { - return nil, fmt.Errorf("%w: beneficiary %s", ErrBlacklistedAccount, common.Address(beneficiaryAddr).Hex()) + return nil, &ErrBlacklistedAccount{Address: beneficiaryAddr, Role: beneficiaryRole} } } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 210b2fc09..8353a6bca 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -930,63 +930,46 @@ func TestOpMCopy(t *testing.T) { } } -func TestSelfdestructBlacklistedAccount(t *testing.T) { - contractAddr := common.HexToAddress("0x1000000000000000000000000000000000000001") - beneficiaryAddr := common.HexToAddress("0x2000000000000000000000000000000000000002") - +func TestBlacklistedAccountSelfdestruct(t *testing.T) { tests := []struct { - name string - contractBlacklisted bool - beneficiaryBlacklisted bool - instruction func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) - expectErr bool - errPart string + name string + blacklistedRole BlacklistRole + instruction func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) + expectErr bool }{ { - name: "opSelfdestruct unrelated to any blacklisted account", - contractBlacklisted: false, - beneficiaryBlacklisted: false, - instruction: opSelfdestruct, - expectErr: false, + name: "opSelfdestruct unrelated to any blacklisted account", + instruction: opSelfdestruct, + expectErr: false, }, { - name: "opSelfdestruct contract is blacklisted", - contractBlacklisted: true, - beneficiaryBlacklisted: false, - instruction: opSelfdestruct, - expectErr: true, - errPart: fmt.Sprintf("contract %s", contractAddr.Hex()), + name: "opSelfdestruct contract is blacklisted", + blacklistedRole: contractRole, + instruction: opSelfdestruct, + expectErr: true, }, { - name: "opSelfdestruct beneficiary is blacklisted", - contractBlacklisted: false, - beneficiaryBlacklisted: true, - instruction: opSelfdestruct, - expectErr: true, - errPart: fmt.Sprintf("beneficiary %s", beneficiaryAddr.Hex()), + name: "opSelfdestruct beneficiary is blacklisted", + blacklistedRole: beneficiaryRole, + instruction: opSelfdestruct, + expectErr: true, }, { - name: "opSelfdestruct6780 unrelated to any blacklisted account", - contractBlacklisted: false, - beneficiaryBlacklisted: false, - instruction: opSelfdestruct6780, - expectErr: false, + name: "opSelfdestruct6780 unrelated to any blacklisted account", + instruction: opSelfdestruct6780, + expectErr: false, }, { - name: "opSelfdestruct6780 contract is blacklisted", - contractBlacklisted: true, - beneficiaryBlacklisted: false, - instruction: opSelfdestruct6780, - expectErr: true, - errPart: fmt.Sprintf("contract %s", contractAddr.Hex()), + name: "opSelfdestruct6780 contract is blacklisted", + blacklistedRole: contractRole, + instruction: opSelfdestruct6780, + expectErr: true, }, { - name: "opSelfdestruct6780 beneficiary is blacklisted", - contractBlacklisted: false, - beneficiaryBlacklisted: true, - instruction: opSelfdestruct6780, - expectErr: true, - errPart: fmt.Sprintf("beneficiary %s", beneficiaryAddr.Hex()), + name: "opSelfdestruct6780 beneficiary is blacklisted", + blacklistedRole: beneficiaryRole, + instruction: opSelfdestruct6780, + expectErr: true, }, } @@ -1004,15 +987,19 @@ func TestSelfdestructBlacklistedAccount(t *testing.T) { evmInterpreter = env.interpreter ) - statedb.CreateAccount(contractAddr) - if tc.contractBlacklisted { - statedb.SetBlacklisted(contractAddr) + testAccts := map[BlacklistRole]*account{ + contractRole: newAccount(statedb), + beneficiaryRole: newAccount(statedb), } - statedb.CreateAccount(beneficiaryAddr) - if tc.beneficiaryBlacklisted { - statedb.SetBlacklisted(beneficiaryAddr) + + blacklistedAcct, ok := testAccts[tc.blacklistedRole] + if ok { + statedb.SetBlacklisted(blacklistedAcct.address) } + contractAddr := testAccts[contractRole].address + beneficiaryAddr := testAccts[beneficiaryRole].address + stack.push(new(uint256.Int).SetBytes(beneficiaryAddr.Bytes())) contract := Contract{ self: contractRef{addr: contractAddr}, @@ -1023,8 +1010,13 @@ func TestSelfdestructBlacklistedAccount(t *testing.T) { } if tc.expectErr { - require.ErrorIs(t, err, ErrBlacklistedAccount) - require.Contains(t, err.Error(), tc.errPart) + require.Error(t, err) + + var haveErr *ErrBlacklistedAccount + require.ErrorAs(t, err, &haveErr) + + require.Equal(t, tc.blacklistedRole, haveErr.Role) + require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) } From 7b3f795d7e79b9ef2fe066cfe67dacfdf4893ca8 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Tue, 18 Nov 2025 11:28:14 +0900 Subject: [PATCH 21/30] test: assign initial balance to test accounts in EVM tests --- core/vm/evm_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 65c55fddd..6304248b0 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -42,7 +42,7 @@ func newAccount(statedb StateDB) *account { account := &account{key, address} statedb.CreateAccount(account.address) - statedb.AddBalance(account.address, uint256.NewInt(0)) + statedb.AddBalance(account.address, uint256.NewInt(1000000000000000000)) return account } From 81c776504e8b19809bde4d8b2187b110a58ef2ba Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Tue, 18 Nov 2025 11:50:46 +0900 Subject: [PATCH 22/30] test: add block number to EVM BlockContext --- core/vm/instructions_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 8353a6bca..8a6ad8ca1 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -980,7 +980,7 @@ func TestBlacklistedAccountSelfdestruct(t *testing.T) { var ( statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestWBFTChainConfig, Config{}) + env = NewEVM(BlockContext{BlockNumber: new(big.Int).SetUint64(1)}, TxContext{}, statedb, params.TestWBFTChainConfig, Config{}) stack = newstack() mem = NewMemory() pc = uint64(0) From dcef9dea52748a3551e27175659717a20c25f195 Mon Sep 17 00:00:00 2001 From: eomti-wm Date: Tue, 18 Nov 2025 12:09:47 +0900 Subject: [PATCH 23/30] test: check correct error message --- systemcontracts/test/coin_adapter_test.go | 25 ++++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/systemcontracts/test/coin_adapter_test.go b/systemcontracts/test/coin_adapter_test.go index 861f9177c..b113074d9 100644 --- a/systemcontracts/test/coin_adapter_test.go +++ b/systemcontracts/test/coin_adapter_test.go @@ -816,7 +816,8 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { normalAccount = NewEOA() blacklistedAccount = NewEOA() - expectedErrMsg = "account is blacklisted" + expectedRevertMsg = "account is blacklisted" // Revert triggered in contract(NativeCoinAdapter) + expectedErrMsg = "blacklisted sender" // Error occurred in EVM ) councilMember := NewEOA() @@ -858,7 +859,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { beforeBalance := g.BalanceOf(t, blacklistedAccount.Address) // mint to blacklisted address - ExpectedRevert(t, g.ExpectedFail(g.Mint(t, minter, blacklistedAccount.Address, initialBalance)), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.Mint(t, minter, blacklistedAccount.Address, initialBalance)), expectedRevertMsg) require.True(t, beforeBalance.Cmp(g.BalanceOf(t, blacklistedAccount.Address)) == 0) }) @@ -876,8 +877,8 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { } // transfer to blacklisted address - ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, from, to.Address, new(big.Int))), expectedErrMsg) - ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, from, to.Address, amount)), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, from, to.Address, new(big.Int))), expectedRevertMsg) + ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, from, to.Address, amount)), expectedRevertMsg) // blacklisted address transfer ExpectedRevert(t, g.ExpectedFail(g.Transfer(t, to, from.Address, amount)), expectedErrMsg) @@ -903,14 +904,14 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { require.NoError(t, err) } // owner -> blacklist - ExpectedRevert(t, g.ExpectedFail(g.Approve(t, owner, spender.Address, approveAmount)), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.Approve(t, owner, spender.Address, approveAmount)), expectedRevertMsg) // blacklist -> owner ExpectedRevert(t, g.ExpectedFail(g.Approve(t, spender, owner.Address, approveAmount)), expectedErrMsg) // transfer to blacklist - ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, minter, owner.Address, spender.Address, transferAmount)), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, minter, owner.Address, spender.Address, transferAmount)), expectedRevertMsg) // transfer from blacklist - ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, owner, spender.Address, minter.Address, new(big.Int))), expectedErrMsg) + ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, owner, spender.Address, minter.Address, new(big.Int))), expectedRevertMsg) // msg.sender is blacklist ExpectedRevert(t, g.ExpectedFail(g.TransferFrom(t, spender, owner.Address, minter.Address, new(big.Int))), expectedErrMsg) }) @@ -926,7 +927,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.Permit(t, minter, owner.Address, spender.Address, approveAmount, nil, permitSig)), - expectedErrMsg, + expectedRevertMsg, ) } // onwer is blacklisted @@ -935,7 +936,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.Permit(t, minter, spender.Address, owner.Address, approveAmount, nil, permitSig)), - expectedErrMsg, + expectedRevertMsg, ) } // msg.sender is blacklisted - should fail due to Go-level sender validation @@ -976,7 +977,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.TransferWithAuthorization(t, minter, from.Address, to.Address, transferAmount, nil, nil, transferNonce, transferSig)), - expectedErrMsg, + expectedRevertMsg, ) } // transfer from blacklist @@ -986,7 +987,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.TransferWithAuthorization(t, minter, to.Address, from.Address, transferAmount, nil, nil, transferNonce, transferSig)), - expectedErrMsg, + expectedRevertMsg, ) } // msg.sender is blacklisted - should fail due to Go-level sender validation @@ -1037,7 +1038,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { ExpectedRevert(t, g.ExpectedFail(g.ReceiveWithAuthorization(t, from, to.Address, transferAmount, nil, nil, receiveNonce, receiveSig)), - expectedErrMsg, + expectedRevertMsg, ) } // not blacklisted From a0b69c17651f51c376e59ce9f7723d1fd44ba761 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Tue, 18 Nov 2025 16:12:58 +0900 Subject: [PATCH 24/30] feat: enforce blacklist checks in DelegateCall, CallCode, and StaticCall --- core/vm/evm.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/vm/evm.go b/core/vm/evm.go index 770b8af43..6f3ad8ddc 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -308,6 +308,15 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + // Fail if the caller or the target address is blacklisted under Anzeon rules + if evm.chainRules.IsAnzeon { + if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { + return nil, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} + } + if evm.StateDB.IsBlacklisted(addr) { + return nil, gas, &ErrBlacklistedAccount{Address: addr, Role: targetRole} + } + } // Fail if we're trying to transfer more than the available balance // Note although it's noop to transfer X ether to caller itself. But // if caller doesn't have enough balance, it would be an error to allow @@ -358,6 +367,15 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + // Fail if the caller or the target address is blacklisted under Anzeon rules + if evm.chainRules.IsAnzeon { + if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { + return nil, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} + } + if evm.StateDB.IsBlacklisted(addr) { + return nil, gas, &ErrBlacklistedAccount{Address: addr, Role: targetRole} + } + } var snapshot = evm.StateDB.Snapshot() // Invoke tracer hooks that signal entering/exiting a call frame @@ -403,6 +421,15 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } + // Fail if the caller or the target address is blacklisted under Anzeon rules + if evm.chainRules.IsAnzeon { + if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { + return nil, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} + } + if evm.StateDB.IsBlacklisted(addr) { + return nil, gas, &ErrBlacklistedAccount{Address: addr, Role: targetRole} + } + } // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced // after all empty accounts were deleted, so this is not required. However, if we omit this, From ab25bc5839b3cb8c64bdfb4c0b7e59390382e332 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Tue, 18 Nov 2025 16:52:43 +0900 Subject: [PATCH 25/30] test: add blacklist enforcement tests for DelegateCall, CallCode, and StaticCall --- core/error.go | 1 + core/txpool/validation_test.go | 18 ++--- core/vm/errors.go | 1 + core/vm/evm_test.go | 119 +++++++++++++++++++++++++++------ core/vm/instructions_test.go | 18 ++--- 5 files changed, 121 insertions(+), 36 deletions(-) diff --git a/core/error.go b/core/error.go index 7deb7054d..10be29d19 100644 --- a/core/error.go +++ b/core/error.go @@ -117,6 +117,7 @@ var ( type BlacklistRole string const ( + NoneRole BlacklistRole = "" // sentinel: no blacklisted role SenderRole BlacklistRole = "sender" RecipientRole BlacklistRole = "recipient" FeePayerRole BlacklistRole = "feePayer" diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go index cbc47b476..92c7fd83d 100644 --- a/core/txpool/validation_test.go +++ b/core/txpool/validation_test.go @@ -137,8 +137,9 @@ func TestBlacklistedAccountTx(t *testing.T) { expectErr bool }{ { - name: "unrelated to any blacklisted account", - expectErr: false, + name: "unrelated to any blacklisted account", + blacklistedRole: core.NoneRole, + expectErr: false, }, { name: "sender is blacklisted", @@ -163,8 +164,8 @@ func TestBlacklistedAccountTx(t *testing.T) { core.RecipientRole: newAccount(statedb), } - blacklistedAcct, ok := testAccts[tc.blacklistedRole] - if ok { + if tc.blacklistedRole != core.NoneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] statedb.SetBlacklisted(blacklistedAcct.address) } @@ -197,8 +198,9 @@ func TestBlacklistedAccountTx(t *testing.T) { expectErr bool }{ { - name: "unrelated to any blacklisted account", - expectErr: false, + name: "unrelated to any blacklisted account", + blacklistedRole: core.NoneRole, + expectErr: false, }, { name: "sender is blacklisted", @@ -229,8 +231,8 @@ func TestBlacklistedAccountTx(t *testing.T) { core.FeePayerRole: newAccount(statedb), } - blacklistedAcct, ok := testAccts[tc.blacklistedRole] - if ok { + if tc.blacklistedRole != core.NoneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] statedb.SetBlacklisted(blacklistedAcct.address) } diff --git a/core/vm/errors.go b/core/vm/errors.go index 7190401c8..e9dd490fc 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -83,6 +83,7 @@ func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: type BlacklistRole string const ( + noneRole BlacklistRole = "" // sentinel: no blacklisted role callerRole BlacklistRole = "caller" targetRole BlacklistRole = "target" contractRole BlacklistRole = "contract" diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 6304248b0..6bdf05fb7 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -31,6 +31,8 @@ import ( "github.com/stretchr/testify/require" ) +const testGas = 100000 + type account struct { privKey *ecdsa.PrivateKey address common.Address @@ -42,7 +44,7 @@ func newAccount(statedb StateDB) *account { account := &account{key, address} statedb.CreateAccount(account.address) - statedb.AddBalance(account.address, uint256.NewInt(1000000000000000000)) + statedb.AddBalance(account.address, uint256.NewInt(params.Ether)) return account } @@ -60,26 +62,105 @@ func newTestEvm(statedb StateDB) *EVM { return NewEVM(vmctx, TxContext{}, statedb, params.TestWBFTChainConfig, Config{}) } +type callFn func(evm *EVM, caller *Contract, target common.Address) error + +func invokeCall(evm *EVM, caller *Contract, target common.Address) error { + _, _, err := evm.Call(caller, target, []byte{}, testGas, uint256.NewInt(0)) + return err +} + +func invokeDelegateCall(evm *EVM, caller *Contract, target common.Address) error { + _, _, err := evm.DelegateCall(caller, target, []byte{}, testGas) + return err +} + +func invokeCallCode(evm *EVM, caller *Contract, target common.Address) error { + _, _, err := evm.CallCode(caller, target, []byte{}, testGas, uint256.NewInt(0)) + return err +} + +func invokeStaticCall(evm *EVM, caller *Contract, target common.Address) error { + _, _, err := evm.StaticCall(caller, target, []byte{}, testGas) + return err +} + func TestBlacklistedAccountExecution(t *testing.T) { t.Run("Call", func(t *testing.T) { - t.Parallel() - tests := []struct { name string + invoke callFn blacklistedRole BlacklistRole expectErr bool }{ { - name: "unrelated to any blacklisted account", - expectErr: false, + name: "Call: unrelated to any blacklisted account", + invoke: invokeCall, + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "Call: caller is blacklisted", + invoke: invokeCall, + blacklistedRole: callerRole, + expectErr: true, + }, + { + name: "Call: target is blacklisted", + invoke: invokeCall, + blacklistedRole: targetRole, + expectErr: true, + }, + { + name: "DelegateCall: unrelated to any blacklisted account", + invoke: invokeDelegateCall, + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "DelegateCall: caller is blacklisted", + invoke: invokeDelegateCall, + blacklistedRole: callerRole, + expectErr: true, + }, + { + name: "DelegateCall: target is blacklisted", + invoke: invokeDelegateCall, + blacklistedRole: targetRole, + expectErr: true, + }, + { + name: "CallCode: unrelated to any blacklisted account", + invoke: invokeCallCode, + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "CallCode: caller is blacklisted", + invoke: invokeCallCode, + blacklistedRole: callerRole, + expectErr: true, + }, + { + name: "CallCode: target is blacklisted", + invoke: invokeCallCode, + blacklistedRole: targetRole, + expectErr: true, }, { - name: "caller is blacklisted", + name: "StaticCall: unrelated to any blacklisted account", + invoke: invokeStaticCall, + blacklistedRole: noneRole, + expectErr: false, + }, + { + name: "StaticCall: caller is blacklisted", + invoke: invokeStaticCall, blacklistedRole: callerRole, expectErr: true, }, { - name: "target is blacklisted", + name: "StaticCall: target is blacklisted", + invoke: invokeStaticCall, blacklistedRole: targetRole, expectErr: true, }, @@ -98,17 +179,17 @@ func TestBlacklistedAccountExecution(t *testing.T) { targetRole: newAccount(statedb), } - blacklistedAcct, ok := testAccts[tc.blacklistedRole] - if ok { + if tc.blacklistedRole != noneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] statedb.SetBlacklisted(blacklistedAcct.address) } caller := testAccts[callerRole] target := testAccts[targetRole] - callerRef := AccountRef(caller.address) + callerRef := NewContract(AccountRef(caller.address), AccountRef(caller.address), uint256.NewInt(0), 0) - _, _, err := evm.Call(callerRef, target.address, []byte{}, 0, uint256.NewInt(0)) + err := tc.invoke(evm, callerRef, target.address) if tc.expectErr { require.Error(t, err) @@ -125,19 +206,18 @@ func TestBlacklistedAccountExecution(t *testing.T) { }) t.Run("Create", func(t *testing.T) { - t.Parallel() - tests := []struct { name string blacklistedRole BlacklistRole expectErr bool }{ { - name: "unrelated to any blacklisted account", - expectErr: false, + name: "Create: unrelated to any blacklisted account", + blacklistedRole: noneRole, + expectErr: false, }, { - name: "caller is blacklisted", + name: "Create: caller is blacklisted", blacklistedRole: callerRole, expectErr: true, }, @@ -155,8 +235,8 @@ func TestBlacklistedAccountExecution(t *testing.T) { callerRole: newAccount(statedb), } - blacklistedAcct, ok := testAccts[tc.blacklistedRole] - if ok { + if tc.blacklistedRole != noneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] statedb.SetBlacklisted(blacklistedAcct.address) } @@ -167,8 +247,7 @@ func TestBlacklistedAccountExecution(t *testing.T) { codeAndHash := codeAndHash{ code: constructorCode, } - - _, _, _, err := evm.create(callerRef, &codeAndHash, 0, uint256.NewInt(0), common.Address{}, CREATE) + _, _, _, err := evm.create(callerRef, &codeAndHash, testGas, uint256.NewInt(0), common.Address{}, CREATE) if tc.expectErr { require.Error(t, err) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 8a6ad8ca1..a0f526cba 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -938,9 +938,10 @@ func TestBlacklistedAccountSelfdestruct(t *testing.T) { expectErr bool }{ { - name: "opSelfdestruct unrelated to any blacklisted account", - instruction: opSelfdestruct, - expectErr: false, + name: "opSelfdestruct unrelated to any blacklisted account", + blacklistedRole: noneRole, + instruction: opSelfdestruct, + expectErr: false, }, { name: "opSelfdestruct contract is blacklisted", @@ -955,9 +956,10 @@ func TestBlacklistedAccountSelfdestruct(t *testing.T) { expectErr: true, }, { - name: "opSelfdestruct6780 unrelated to any blacklisted account", - instruction: opSelfdestruct6780, - expectErr: false, + name: "opSelfdestruct6780 unrelated to any blacklisted account", + blacklistedRole: noneRole, + instruction: opSelfdestruct6780, + expectErr: false, }, { name: "opSelfdestruct6780 contract is blacklisted", @@ -992,8 +994,8 @@ func TestBlacklistedAccountSelfdestruct(t *testing.T) { beneficiaryRole: newAccount(statedb), } - blacklistedAcct, ok := testAccts[tc.blacklistedRole] - if ok { + if tc.blacklistedRole != noneRole { + blacklistedAcct := testAccts[tc.blacklistedRole] statedb.SetBlacklisted(blacklistedAcct.address) } From ea30e1054d7371e7e91ffbcc2d1148bbbf80a0ce Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Tue, 18 Nov 2025 17:03:26 +0900 Subject: [PATCH 26/30] test: fix typos --- core/vm/instructions_test.go | 2 +- systemcontracts/test/coin_adapter_test.go | 42 +++++++++++------------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index a0f526cba..bb325d034 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -644,7 +644,7 @@ func BenchmarkOpKeccak256(bench *testing.B) { } } -func TestCreate2Addreses(t *testing.T) { +func TestCreate2Addresses(t *testing.T) { type testcase struct { origin string salt string diff --git a/systemcontracts/test/coin_adapter_test.go b/systemcontracts/test/coin_adapter_test.go index b113074d9..f4bd30803 100644 --- a/systemcontracts/test/coin_adapter_test.go +++ b/systemcontracts/test/coin_adapter_test.go @@ -475,16 +475,16 @@ func TestNativeCoinAdapter(t *testing.T) { transferSig, r, s, v := g.BuildTransferWithAuthSig(t, from, to.Address, transferAmount, nil, nil, transferNonce) // 0 - MAX_UINT_256 // transfer by r,s,v { - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk( g.TransferWithAuthorization(t, minter1, from.Address, to.Address, transferAmount, nil, nil, transferNonce, v, r, s), ) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - require.True(t, new(big.Int).Add(balnaceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + require.True(t, new(big.Int).Add(balanceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) // approval event approvalEvent := findEvent("Transfer", receipt.Logs) @@ -541,14 +541,14 @@ func TestNativeCoinAdapter(t *testing.T) { validBefore := new(big.Int).SetUint64(block.Time() + 100) transferSig, _, _, _ = g.BuildTransferWithAuthSig(t, from, to.Address, transferAmount, validAfter, validBefore, transferNonce) - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk(g.TransferWithAuthorization(t, minter1, from.Address, to.Address, transferAmount, validAfter, validBefore, transferNonce, transferSig)) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - require.True(t, new(big.Int).Add(balnaceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + require.True(t, new(big.Int).Add(balanceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) // transfer event transferEvent := findEvent("Transfer", receipt.Logs) @@ -568,16 +568,16 @@ func TestNativeCoinAdapter(t *testing.T) { receiveSig, r, s, v := g.BuildReceiveWithAuthSig(t, from, to.Address, transferAmount, nil, nil, receiveNonce) // 0 - MAX_UINT_256 // receive by r,s,v { - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk( g.ReceiveWithAuthorization(t, to, from.Address, transferAmount, nil, nil, receiveNonce, v, r, s), ) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - expectedBalance := new(big.Int).Sub(new(big.Int).Add(balnaceTo, transferAmount), calcGasCost(receipt)) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + expectedBalance := new(big.Int).Sub(new(big.Int).Add(balanceTo, transferAmount), calcGasCost(receipt)) require.True(t, expectedBalance.Cmp(g.BalanceOf(t, to.Address)) == 0) // approval event @@ -651,16 +651,16 @@ func TestNativeCoinAdapter(t *testing.T) { validBefore := new(big.Int).SetUint64(block.Time() + 100) receiveSig, _, _, _ = g.BuildReceiveWithAuthSig(t, from, to.Address, transferAmount, validAfter, validBefore, receiveNonce) - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk( g.ReceiveWithAuthorization(t, to, from.Address, transferAmount, validAfter, validBefore, receiveNonce, receiveSig), ) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - expectedBalance := new(big.Int).Sub(new(big.Int).Add(balnaceTo, transferAmount), calcGasCost(receipt)) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + expectedBalance := new(big.Int).Sub(new(big.Int).Add(balanceTo, transferAmount), calcGasCost(receipt)) require.True(t, expectedBalance.Cmp(g.BalanceOf(t, to.Address)) == 0) // transfer event @@ -717,14 +717,14 @@ func TestNativeCoinAdapter(t *testing.T) { // transfer { - balnaceFrom := g.BalanceOf(t, from.Address) - balnaceTo := g.BalanceOf(t, to.Address) + balanceFrom := g.BalanceOf(t, from.Address) + balanceTo := g.BalanceOf(t, to.Address) receipt, err := g.ExpectedOk(g.TransferWithAuthorization(t, minter1, from.Address, to.Address, transferAmount, nil, nil, expectedFailNonce, transferSig)) require.NoError(t, err) - require.True(t, new(big.Int).Sub(balnaceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) - require.True(t, new(big.Int).Add(balnaceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) + require.True(t, new(big.Int).Sub(balanceFrom, transferAmount).Cmp(g.BalanceOf(t, from.Address)) == 0) + require.True(t, new(big.Int).Add(balanceTo, transferAmount).Cmp(g.BalanceOf(t, to.Address)) == 0) // transfer event transferEvent := findEvent("Transfer", receipt.Logs) @@ -930,7 +930,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { expectedRevertMsg, ) } - // onwer is blacklisted + // owner is blacklisted { permitSig, _, _, _ := g.BuildPermitSig(t, spender, owner.Address, approveAmount, nil) // deadline == MAX_UINT_256 From e4e5c94d6b3fca3a684dc8f5be25007bb771a671 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Tue, 18 Nov 2025 20:26:04 +0900 Subject: [PATCH 27/30] refactor: remove role field from ErrBlacklistedAccount --- core/error.go | 12 +---- core/state_transition.go | 6 +-- core/txpool/validation.go | 6 +-- core/txpool/validation_test.go | 58 +++++++++++++---------- core/vm/errors.go | 13 +---- core/vm/evm.go | 57 ++++++++++------------ core/vm/evm_test.go | 14 ++++-- core/vm/instructions.go | 20 ++------ core/vm/instructions_test.go | 2 - systemcontracts/test/coin_adapter_test.go | 2 +- 10 files changed, 79 insertions(+), 111 deletions(-) diff --git a/core/error.go b/core/error.go index 10be29d19..fe9fdb452 100644 --- a/core/error.go +++ b/core/error.go @@ -114,20 +114,10 @@ var ( ErrBlobTxCreate = errors.New("blob transaction of type create") ) -type BlacklistRole string - -const ( - NoneRole BlacklistRole = "" // sentinel: no blacklisted role - SenderRole BlacklistRole = "sender" - RecipientRole BlacklistRole = "recipient" - FeePayerRole BlacklistRole = "feePayer" -) - type ErrBlacklistedAccount struct { Address common.Address - Role BlacklistRole } func (e *ErrBlacklistedAccount) Error() string { - return fmt.Sprintf("blacklisted %s: %s", string(e.Role), e.Address.Hex()) + return fmt.Sprintf("blacklisted account: %s", e.Address.Hex()) } diff --git a/core/state_transition.go b/core/state_transition.go index 852fe55bc..d644ee44a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -478,12 +478,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if rules.IsAnzeon { // Check sender blacklist if st.state.IsBlacklisted(msg.From) { - return nil, &ErrBlacklistedAccount{Address: msg.From, Role: SenderRole} + return nil, &ErrBlacklistedAccount{Address: msg.From} } // Check recipient blacklist (only for transfers, not for contract creation) if msg.To != nil && st.state.IsBlacklisted(*msg.To) { - return nil, &ErrBlacklistedAccount{Address: *msg.To, Role: RecipientRole} + return nil, &ErrBlacklistedAccount{Address: *msg.To} } } @@ -534,7 +534,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.msg.FeePayer != nil { payer = *st.msg.FeePayer if rules.IsAnzeon && st.state.IsBlacklisted(payer) { - return nil, &ErrBlacklistedAccount{Address: payer, Role: FeePayerRole} + return nil, &ErrBlacklistedAccount{Address: payer} } } st.evm.AddTransferLog(payer, st.evm.Context.Coinbase, fee) diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 239dfbe2d..a0fea986a 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -225,10 +225,10 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op // Ensure that neither the sender nor the recipient is blacklisted if opts.Config.AnzeonEnabled() { if opts.State.IsBlacklisted(from) { - return &core.ErrBlacklistedAccount{Address: from, Role: core.SenderRole} + return &core.ErrBlacklistedAccount{Address: from} } if to := tx.To(); to != nil && opts.State.IsBlacklisted(*to) { - return &core.ErrBlacklistedAccount{Address: *to, Role: core.RecipientRole} + return &core.ErrBlacklistedAccount{Address: *to} } } next := opts.State.GetNonce(from) @@ -247,7 +247,7 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op feePayer := tx.FeePayer() // Ensure that the fee payer is not blacklisted if opts.Config.AnzeonEnabled() && opts.State.IsBlacklisted(*feePayer) { - return &core.ErrBlacklistedAccount{Address: *feePayer, Role: core.FeePayerRole} + return &core.ErrBlacklistedAccount{Address: *feePayer} } if opts.State.GetBalance(from).ToBig().Cmp(tx.Value()) < 0 { return ErrSenderInsufficientFunds diff --git a/core/txpool/validation_test.go b/core/txpool/validation_test.go index 92c7fd83d..599bda4ea 100644 --- a/core/txpool/validation_test.go +++ b/core/txpool/validation_test.go @@ -33,6 +33,15 @@ import ( "github.com/stretchr/testify/require" ) +type BlacklistRole uint8 + +const ( + NoneRole BlacklistRole = iota + SenderRole + RecipientRole + FeePayerRole +) + type account struct { privKey *ecdsa.PrivateKey address common.Address @@ -133,22 +142,22 @@ func TestBlacklistedAccountTx(t *testing.T) { t.Run("DynamicFeeTx", func(t *testing.T) { tests := []struct { name string - blacklistedRole core.BlacklistRole + blacklistedRole BlacklistRole expectErr bool }{ { name: "unrelated to any blacklisted account", - blacklistedRole: core.NoneRole, + blacklistedRole: NoneRole, expectErr: false, }, { name: "sender is blacklisted", - blacklistedRole: core.SenderRole, + blacklistedRole: SenderRole, expectErr: true, }, { name: "recipient is blacklisted", - blacklistedRole: core.RecipientRole, + blacklistedRole: RecipientRole, expectErr: true, }, } @@ -159,18 +168,18 @@ func TestBlacklistedAccountTx(t *testing.T) { t.Parallel() statedb, opts := newTestState() - testAccts := map[core.BlacklistRole]*account{ - core.SenderRole: newAccount(statedb), - core.RecipientRole: newAccount(statedb), + testAccts := map[BlacklistRole]*account{ + SenderRole: newAccount(statedb), + RecipientRole: newAccount(statedb), } - if tc.blacklistedRole != core.NoneRole { + if tc.blacklistedRole != NoneRole { blacklistedAcct := testAccts[tc.blacklistedRole] statedb.SetBlacklisted(blacklistedAcct.address) } - sender := testAccts[core.SenderRole] - recipient := testAccts[core.RecipientRole] + sender := testAccts[SenderRole] + recipient := testAccts[RecipientRole] tx := newDynamicFeeTx(&recipient.address) signedTx, _ := signDynamicFeeTx(tx, sender) @@ -182,7 +191,6 @@ func TestBlacklistedAccountTx(t *testing.T) { var haveErr *core.ErrBlacklistedAccount require.ErrorAs(t, err, &haveErr) - require.Equal(t, tc.blacklistedRole, haveErr.Role) require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) @@ -194,27 +202,27 @@ func TestBlacklistedAccountTx(t *testing.T) { t.Run("FeeDelegateDynamicFeeTx", func(t *testing.T) { tests := []struct { name string - blacklistedRole core.BlacklistRole + blacklistedRole BlacklistRole expectErr bool }{ { name: "unrelated to any blacklisted account", - blacklistedRole: core.NoneRole, + blacklistedRole: NoneRole, expectErr: false, }, { name: "sender is blacklisted", - blacklistedRole: core.SenderRole, + blacklistedRole: SenderRole, expectErr: true, }, { name: "recipient is blacklisted", - blacklistedRole: core.RecipientRole, + blacklistedRole: RecipientRole, expectErr: true, }, { name: "feePayer is blacklisted", - blacklistedRole: core.FeePayerRole, + blacklistedRole: FeePayerRole, expectErr: true, }, } @@ -225,20 +233,20 @@ func TestBlacklistedAccountTx(t *testing.T) { t.Parallel() statedb, opts := newTestState() - testAccts := map[core.BlacklistRole]*account{ - core.SenderRole: newAccount(statedb), - core.RecipientRole: newAccount(statedb), - core.FeePayerRole: newAccount(statedb), + testAccts := map[BlacklistRole]*account{ + SenderRole: newAccount(statedb), + RecipientRole: newAccount(statedb), + FeePayerRole: newAccount(statedb), } - if tc.blacklistedRole != core.NoneRole { + if tc.blacklistedRole != NoneRole { blacklistedAcct := testAccts[tc.blacklistedRole] statedb.SetBlacklisted(blacklistedAcct.address) } - sender := testAccts[core.SenderRole] - recipient := testAccts[core.RecipientRole] - feePayer := testAccts[core.FeePayerRole] + sender := testAccts[SenderRole] + recipient := testAccts[RecipientRole] + feePayer := testAccts[FeePayerRole] tx := newDynamicFeeTx(&recipient.address) signedTx, _ := signDynamicFeeTx(tx, sender) @@ -251,8 +259,6 @@ func TestBlacklistedAccountTx(t *testing.T) { var haveErr *core.ErrBlacklistedAccount require.ErrorAs(t, err, &haveErr) - - require.Equal(t, tc.blacklistedRole, haveErr.Role) require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) diff --git a/core/vm/errors.go b/core/vm/errors.go index e9dd490fc..f8948d9bf 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -80,21 +80,10 @@ type ErrInvalidOpCode struct { func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } -type BlacklistRole string - -const ( - noneRole BlacklistRole = "" // sentinel: no blacklisted role - callerRole BlacklistRole = "caller" - targetRole BlacklistRole = "target" - contractRole BlacklistRole = "contract" - beneficiaryRole BlacklistRole = "beneficiary" -) - type ErrBlacklistedAccount struct { Address common.Address - Role BlacklistRole } func (e *ErrBlacklistedAccount) Error() string { - return fmt.Sprintf("blacklisted %s: %s", string(e.Role), e.Address.Hex()) + return fmt.Sprintf("blacklisted account: %s", e.Address.Hex()) } diff --git a/core/vm/evm.go b/core/vm/evm.go index 6f3ad8ddc..5a4e2166d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -195,14 +195,8 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - // Fail if the caller or the target address is blacklisted under Anzeon rules - if evm.chainRules.IsAnzeon { - if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { - return nil, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} - } - if evm.StateDB.IsBlacklisted(addr) { - return nil, gas, &ErrBlacklistedAccount{Address: addr, Role: targetRole} - } + if err := evm.checkBlacklisted(caller.Address(), addr); err != nil { + return nil, gas, err } m, isNativeManager := evm.nativeManager(addr) p, isPrecompile := evm.precompile(addr) @@ -308,14 +302,8 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - // Fail if the caller or the target address is blacklisted under Anzeon rules - if evm.chainRules.IsAnzeon { - if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { - return nil, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} - } - if evm.StateDB.IsBlacklisted(addr) { - return nil, gas, &ErrBlacklistedAccount{Address: addr, Role: targetRole} - } + if err := evm.checkBlacklisted(caller.Address(), addr); err != nil { + return nil, gas, err } // Fail if we're trying to transfer more than the available balance // Note although it's noop to transfer X ether to caller itself. But @@ -367,14 +355,8 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - // Fail if the caller or the target address is blacklisted under Anzeon rules - if evm.chainRules.IsAnzeon { - if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { - return nil, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} - } - if evm.StateDB.IsBlacklisted(addr) { - return nil, gas, &ErrBlacklistedAccount{Address: addr, Role: targetRole} - } + if err := evm.checkBlacklisted(caller.Address(), addr); err != nil { + return nil, gas, err } var snapshot = evm.StateDB.Snapshot() @@ -421,14 +403,8 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - // Fail if the caller or the target address is blacklisted under Anzeon rules - if evm.chainRules.IsAnzeon { - if callerAddr := caller.Address(); evm.StateDB.IsBlacklisted(callerAddr) { - return nil, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} - } - if evm.StateDB.IsBlacklisted(addr) { - return nil, gas, &ErrBlacklistedAccount{Address: addr, Role: targetRole} - } + if err := evm.checkBlacklisted(caller.Address(), addr); err != nil { + return nil, gas, err } // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced @@ -500,7 +476,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Fail if the caller is blacklisted under Anzeon rules if evm.chainRules.IsAnzeon && evm.StateDB.IsBlacklisted(caller.Address()) { - return nil, common.Address{}, gas, &ErrBlacklistedAccount{Address: caller.Address(), Role: callerRole} + return nil, common.Address{}, gas, &ErrBlacklistedAccount{Address: caller.Address()} } if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, common.Address{}, gas, ErrInsufficientBalance @@ -631,3 +607,18 @@ func (evm *EVM) AddTransferLog(sender, recipient common.Address, amount *uint256 BlockNumber: evm.Context.BlockNumber.Uint64(), }) } + +// Fail if from or to address is blacklisted under Anzeon rules +func (evm *EVM) checkBlacklisted(from common.Address, to common.Address) error { + if !evm.chainRules.IsAnzeon { + return nil + } + + if evm.StateDB.IsBlacklisted(from) { + return &ErrBlacklistedAccount{from} + } + if evm.StateDB.IsBlacklisted(to) { + return &ErrBlacklistedAccount{to} + } + return nil +} diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 6bdf05fb7..3f79473a1 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -33,6 +33,16 @@ import ( const testGas = 100000 +type BlacklistRole uint8 + +const ( + noneRole BlacklistRole = iota + callerRole + targetRole + contractRole + beneficiaryRole +) + type account struct { privKey *ecdsa.PrivateKey address common.Address @@ -195,8 +205,6 @@ func TestBlacklistedAccountExecution(t *testing.T) { var haveErr *ErrBlacklistedAccount require.ErrorAs(t, err, &haveErr) - - require.Equal(t, tc.blacklistedRole, haveErr.Role) require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) @@ -253,8 +261,6 @@ func TestBlacklistedAccountExecution(t *testing.T) { var haveErr *ErrBlacklistedAccount require.ErrorAs(t, err, &haveErr) - - require.Equal(t, tc.blacklistedRole, haveErr.Role) require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f3144f28f..34fe801d9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -801,14 +801,8 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext return nil, ErrWriteProtection } beneficiary := scope.Stack.pop() - // Deny SELFDESTRUCT if the contract or beneficiary is blacklisted under Anzeon rules - if interpreter.evm.chainRules.IsAnzeon { - if contractAddr := scope.Contract.Address(); interpreter.evm.StateDB.IsBlacklisted(contractAddr) { - return nil, &ErrBlacklistedAccount{Address: contractAddr, Role: contractRole} - } - if beneficiaryAddr := beneficiary.Bytes20(); interpreter.evm.StateDB.IsBlacklisted(beneficiaryAddr) { - return nil, &ErrBlacklistedAccount{Address: beneficiaryAddr, Role: beneficiaryRole} - } + if err := interpreter.evm.checkBlacklisted(scope.Contract.Address(), beneficiary.Bytes20()); err != nil { + return nil, err } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) @@ -829,14 +823,8 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon return nil, ErrWriteProtection } beneficiary := scope.Stack.pop() - // Deny SELFDESTRUCT if the contract or beneficiary is blacklisted under Anzeon rules - if interpreter.evm.chainRules.IsAnzeon { - if contractAddr := scope.Contract.Address(); interpreter.evm.StateDB.IsBlacklisted(contractAddr) { - return nil, &ErrBlacklistedAccount{Address: contractAddr, Role: contractRole} - } - if beneficiaryAddr := beneficiary.Bytes20(); interpreter.evm.StateDB.IsBlacklisted(beneficiaryAddr) { - return nil, &ErrBlacklistedAccount{Address: beneficiaryAddr, Role: beneficiaryRole} - } + if err := interpreter.evm.checkBlacklisted(scope.Contract.Address(), beneficiary.Bytes20()); err != nil { + return nil, err } balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance) diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index bb325d034..444cd5e4c 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -1016,8 +1016,6 @@ func TestBlacklistedAccountSelfdestruct(t *testing.T) { var haveErr *ErrBlacklistedAccount require.ErrorAs(t, err, &haveErr) - - require.Equal(t, tc.blacklistedRole, haveErr.Role) require.Equal(t, testAccts[tc.blacklistedRole].address, haveErr.Address) } else { require.NoError(t, err) diff --git a/systemcontracts/test/coin_adapter_test.go b/systemcontracts/test/coin_adapter_test.go index f4bd30803..d2582d397 100644 --- a/systemcontracts/test/coin_adapter_test.go +++ b/systemcontracts/test/coin_adapter_test.go @@ -817,7 +817,7 @@ func TestNativeCoinAdapter_Blacklist(t *testing.T) { blacklistedAccount = NewEOA() expectedRevertMsg = "account is blacklisted" // Revert triggered in contract(NativeCoinAdapter) - expectedErrMsg = "blacklisted sender" // Error occurred in EVM + expectedErrMsg = "blacklisted account" // Error occurred in EVM ) councilMember := NewEOA() From 52458b7c2cb662295fce4de2ce5b60618c9a9f47 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Wed, 19 Nov 2025 11:01:20 +0900 Subject: [PATCH 28/30] refactor: provide parent header directly to verifySigner --- consensus/wbft/engine/engine.go | 22 +++++++++------------- consensus/wbft/engine/engine_test.go | 8 +------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/consensus/wbft/engine/engine.go b/consensus/wbft/engine/engine.go index f851ce584..1a4edf1b2 100644 --- a/consensus/wbft/engine/engine.go +++ b/consensus/wbft/engine/engine.go @@ -287,7 +287,7 @@ func (e *Engine) verifyCascadingFields(chain consensus.ChainHeaderReader, header } // Verify signer - if err := e.verifySigner(chain, header, parents, validators); err != nil { + if err := e.verifySigner(chain, header, parent, validators); err != nil { if !errors.Is(err, wbftcommon.ErrStateUnavailable) { return err } @@ -348,7 +348,7 @@ func (e *Engine) verifyCascadingFields(chain consensus.ChainHeaderReader, header return nil } -func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header, validators wbft.ValidatorSet) error { +func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header, validators wbft.ValidatorSet) error { // Verifying the genesis block is not supported number := header.Number.Uint64() if number == 0 { @@ -366,18 +366,9 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H return wbftcommon.ErrUnauthorized } - // Check parent - var parent *types.Header - if len(parents) > 0 { - parent = parents[len(parents)-1] - } else { - parent = chain.GetHeader(header.ParentHash, number-1) - } - // Ensure that the block's parent has right number and hash - if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { + if parent == nil { return consensus.ErrUnknownAncestor } - state, err := chain.StateAt(parent.Root) if err != nil { return fmt.Errorf("%w: %v", wbftcommon.ErrStateUnavailable, err) @@ -480,7 +471,12 @@ func (e *Engine) VerifySeal(chain consensus.ChainHeaderReader, header *types.Hea return wbftcommon.ErrInvalidDifficulty } - return e.verifySigner(chain, header, nil, validators) + parent := chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + + return e.verifySigner(chain, header, parent, validators) } func (e *Engine) PeriodToNextBlock(blockNumber *big.Int) uint64 { diff --git a/consensus/wbft/engine/engine_test.go b/consensus/wbft/engine/engine_test.go index bbfa6542a..f0ee09843 100644 --- a/consensus/wbft/engine/engine_test.go +++ b/consensus/wbft/engine/engine_test.go @@ -948,12 +948,6 @@ func TestBlacklistedSigner(t *testing.T) { Coinbase: signer.addr, } - var parents []*types.Header - if tc.parentExists { - header.ParentHash = parent.Hash() - parents = []*types.Header{parent} - } - addrs := []common.Address{ signer.addr, } @@ -962,7 +956,7 @@ func TestBlacklistedSigner(t *testing.T) { } validators := validator.NewSet(addrs, blsPubKeys, wbft.NewRoundRobinProposerPolicy()) - err := engine.verifySigner(fc, header, parents, validators) + err := engine.verifySigner(fc, header, parent, validators) if tc.expectErr == nil { require.NoError(t, err) From 2abcb5d7a29a42538439b1ca5c7de2c93562f5c0 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Wed, 19 Nov 2025 11:15:56 +0900 Subject: [PATCH 29/30] refactor: remove redundant parent nil check --- consensus/wbft/engine/engine.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/consensus/wbft/engine/engine.go b/consensus/wbft/engine/engine.go index 1a4edf1b2..dac646594 100644 --- a/consensus/wbft/engine/engine.go +++ b/consensus/wbft/engine/engine.go @@ -366,9 +366,8 @@ func (e *Engine) verifySigner(chain consensus.ChainHeaderReader, header *types.H return wbftcommon.ErrUnauthorized } - if parent == nil { - return consensus.ErrUnknownAncestor - } + // The caller ensures that parent is non-nil. + // It is already validated in the calling context. state, err := chain.StateAt(parent.Root) if err != nil { return fmt.Errorf("%w: %v", wbftcommon.ErrStateUnavailable, err) From 8c01bf844ba1f21095bcda99794a1a4b359a93f3 Mon Sep 17 00:00:00 2001 From: "code0xff(WM)" Date: Wed, 19 Nov 2025 11:43:41 +0900 Subject: [PATCH 30/30] test: remove missing parent case from TestBlacklistedSigner --- consensus/wbft/engine/engine_test.go | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/consensus/wbft/engine/engine_test.go b/consensus/wbft/engine/engine_test.go index f0ee09843..aadbdcb45 100644 --- a/consensus/wbft/engine/engine_test.go +++ b/consensus/wbft/engine/engine_test.go @@ -891,26 +891,17 @@ func TestBlacklistedSigner(t *testing.T) { tests := []struct { name string - parentExists bool blacklisted bool expectErr error errContainsPart string }{ { - name: "missing parent header", - parentExists: false, - blacklisted: false, - expectErr: consensus.ErrUnknownAncestor, - }, - { - name: "not blacklisted signer", - parentExists: true, - blacklisted: false, - expectErr: nil, + name: "not blacklisted signer", + blacklisted: false, + expectErr: nil, }, { name: "blacklisted signer", - parentExists: true, blacklisted: true, expectErr: wbftcommon.ErrBlacklistedSigner, errContainsPart: signer.addr.Hex(), @@ -920,12 +911,9 @@ func TestBlacklistedSigner(t *testing.T) { for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { - var parent *types.Header - if tc.parentExists { - parent = &types.Header{ - Number: big.NewInt(1), - Root: common.Hash{}, - } + parent := &types.Header{ + Number: big.NewInt(1), + Root: common.Hash{}, } mockState, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)