Skip to content

Commit f0e8732

Browse files
committed
fix(core): gate coinbase owner refresh behind prague
Refreshing the coinbase owner during block execution changes fee-credit routing and therefore consensus state. Keep the historical block-level owner semantics before Prague and only re-read the current owner per transaction after Prague. Add unit tests covering both TRC21 and native-fee paths for the pre-Prague and post-Prague behaviors.
1 parent f5fe86c commit f0e8732

2 files changed

Lines changed: 329 additions & 0 deletions

File tree

core/state_processor.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
458458
// End Bypass denylist address
459459

460460
// Apply the transaction to the current state (included in the env)
461+
if config.IsPrague(blockNumber) {
462+
coinbaseOwner = statedb.GetOwner(evm.Context.Coinbase)
463+
}
461464
result, err := ApplyMessage(evm, msg, gp, coinbaseOwner)
462465
if err != nil {
463466
return nil, 0, false, err

core/state_processor_test.go

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,332 @@ func TestApplyTransactionWithEVMStateChangeHooks(t *testing.T) {
598598
}
599599
}
600600

601+
func TestProcessReReadsCoinbaseOwnerPerTransactionAfterPrague(t *testing.T) {
602+
tokenAddr := common.HexToAddress("0x00000000000000000000000000000000000000b0")
603+
tokensFee := map[common.Address]*big.Int{
604+
tokenAddr: new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil),
605+
}
606+
testApplyTransactionCoinbaseOwnerByFork(t, tokenAddr, tokensFee, big.NewInt(0), func(tx *types.Transaction, receipt *types.Receipt, header *types.Header) *big.Int {
607+
return common.GetGasFee(header.Number.Uint64(), receipt.GasUsed)
608+
})
609+
}
610+
611+
func TestProcessKeepsCoinbaseOwnerPerBlockBeforePrague(t *testing.T) {
612+
tokenAddr := common.HexToAddress("0x00000000000000000000000000000000000000b0")
613+
tokensFee := map[common.Address]*big.Int{
614+
tokenAddr: new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil),
615+
}
616+
pragueBlock := new(big.Int).Add(common.TIPTRC21Fee, big.NewInt(2))
617+
testProcessCoinbaseOwnerByFork(t, tokenAddr, tokensFee, pragueBlock, false, func(tx *types.Transaction, receipt *types.Receipt, header *types.Header) *big.Int {
618+
return common.GetGasFee(header.Number.Uint64(), receipt.GasUsed)
619+
})
620+
}
621+
622+
func TestProcessReReadsCoinbaseOwnerPerTransactionAfterPragueNativeFee(t *testing.T) {
623+
recipient := common.HexToAddress("0x00000000000000000000000000000000000000d0")
624+
testApplyTransactionCoinbaseOwnerByFork(t, recipient, nil, big.NewInt(0), func(tx *types.Transaction, receipt *types.Receipt, header *types.Header) *big.Int {
625+
fee := new(big.Int).SetUint64(receipt.GasUsed)
626+
return fee.Mul(fee, tx.EffectiveGasPrice(new(big.Int), header.BaseFee))
627+
})
628+
}
629+
630+
func TestProcessKeepsCoinbaseOwnerPerBlockBeforePragueNativeFee(t *testing.T) {
631+
recipient := common.HexToAddress("0x00000000000000000000000000000000000000d0")
632+
pragueBlock := new(big.Int).Add(common.TIPTRC21Fee, big.NewInt(2))
633+
testProcessCoinbaseOwnerByFork(t, recipient, nil, pragueBlock, false, func(tx *types.Transaction, receipt *types.Receipt, header *types.Header) *big.Int {
634+
fee := new(big.Int).SetUint64(receipt.GasUsed)
635+
return fee.Mul(fee, tx.EffectiveGasPrice(new(big.Int), header.BaseFee))
636+
})
637+
}
638+
639+
func testProcessCoinbaseOwnerByFork(t *testing.T, recipient common.Address, tokensFee map[common.Address]*big.Int, pragueBlock *big.Int, expectCurrentOwner bool, expectedFee func(*types.Transaction, *types.Receipt, *types.Header) *big.Int) {
640+
var (
641+
config = &params.ChainConfig{
642+
ChainID: big.NewInt(1),
643+
HomesteadBlock: big.NewInt(0),
644+
EIP150Block: big.NewInt(0),
645+
EIP155Block: big.NewInt(0),
646+
EIP158Block: big.NewInt(0),
647+
ByzantiumBlock: big.NewInt(0),
648+
ConstantinopleBlock: big.NewInt(0),
649+
PetersburgBlock: big.NewInt(0),
650+
IstanbulBlock: big.NewInt(0),
651+
BerlinBlock: big.NewInt(0),
652+
LondonBlock: big.NewInt(0),
653+
Eip1559Block: big.NewInt(0),
654+
PragueBlock: new(big.Int).Set(pragueBlock),
655+
Ethash: new(params.EthashConfig),
656+
}
657+
chainEngine = ethash.NewFaker()
658+
engine = noOpFinalizeEngine{Engine: chainEngine}
659+
signer = types.LatestSigner(config)
660+
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
661+
sender = crypto.PubkeyToAddress(testKey.PublicKey)
662+
coinbase = common.HexToAddress("0x00000000000000000000000000000000000000c0")
663+
owner1 = common.HexToAddress("0x00000000000000000000000000000000000000a1")
664+
owner2 = common.HexToAddress("0x00000000000000000000000000000000000000a2")
665+
blockNumber = new(big.Int).Add(common.TIPTRC21Fee, common.Big1)
666+
gasTipCap = new(big.Int).Set(common.BaseFee)
667+
gasFeeCap = new(big.Int).Mul(new(big.Int).Set(common.BaseFee), big.NewInt(2))
668+
)
669+
670+
db := rawdb.NewMemoryDatabase()
671+
gspec := &Genesis{
672+
Config: config,
673+
Alloc: types.GenesisAlloc{
674+
sender: {
675+
Balance: big.NewInt(1000000000000000000),
676+
Nonce: 0,
677+
},
678+
},
679+
}
680+
genesis := gspec.MustCommit(db)
681+
blockchain, _ := NewBlockChain(db, nil, gspec, chainEngine, vm.Config{})
682+
defer blockchain.Stop()
683+
684+
statedb, err := blockchain.State()
685+
if err != nil {
686+
t.Fatalf("Failed to get state: %v", err)
687+
}
688+
setCoinbaseOwner(statedb, coinbase, owner1)
689+
690+
tx1, err := types.SignTx(types.NewTx(&types.DynamicFeeTx{
691+
Nonce: 0,
692+
GasTipCap: gasTipCap,
693+
GasFeeCap: gasFeeCap,
694+
Gas: 21000,
695+
To: &recipient,
696+
Value: big.NewInt(0),
697+
}), signer, testKey)
698+
if err != nil {
699+
t.Fatalf("Failed to sign first tx: %v", err)
700+
}
701+
tx2, err := types.SignTx(types.NewTx(&types.DynamicFeeTx{
702+
Nonce: 1,
703+
GasTipCap: gasTipCap,
704+
GasFeeCap: gasFeeCap,
705+
Gas: 21068,
706+
To: &recipient,
707+
Value: big.NewInt(0),
708+
Data: []byte{0x01},
709+
}), signer, testKey)
710+
if err != nil {
711+
t.Fatalf("Failed to sign second tx: %v", err)
712+
}
713+
714+
header := &types.Header{
715+
ParentHash: genesis.Hash(),
716+
Coinbase: coinbase,
717+
Difficulty: big.NewInt(0),
718+
GasLimit: 1000000,
719+
Number: blockNumber,
720+
Time: genesis.Time() + 10,
721+
UncleHash: types.EmptyUncleHash,
722+
BaseFee: new(big.Int).Set(common.BaseFee),
723+
}
724+
block := types.NewBlock(header, &types.Body{Transactions: types.Transactions{tx1, tx2}}, nil, trie.NewStackTrie(nil))
725+
726+
txEndCalls := 0
727+
cfg := vm.Config{Tracer: &tracing.Hooks{
728+
OnTxEnd: func(receipt *types.Receipt, err error) {
729+
txEndCalls++
730+
if txEndCalls == 1 {
731+
setCoinbaseOwner(statedb, coinbase, owner2)
732+
}
733+
},
734+
}}
735+
736+
processor := NewStateProcessor(config, blockchain, engine)
737+
receipts, _, _, err := processor.Process(block, statedb, nil, cfg, tokensFee)
738+
if err != nil {
739+
t.Fatalf("Process failed: %v", err)
740+
}
741+
if len(receipts) != 2 {
742+
t.Fatalf("expected 2 receipts, got %d", len(receipts))
743+
}
744+
if txEndCalls != 2 {
745+
t.Fatalf("expected 2 OnTxEnd calls, got %d", txEndCalls)
746+
}
747+
748+
owner1Balance := statedb.GetBalance(owner1)
749+
owner2Balance := statedb.GetBalance(owner2)
750+
if owner1Balance.Sign() <= 0 {
751+
t.Fatalf("expected initial owner to receive first tx fee credit, have %v", owner1Balance)
752+
}
753+
if expectCurrentOwner && owner2Balance.Sign() <= 0 {
754+
t.Fatalf("expected updated owner to receive second tx fee credit, have %v", owner2Balance)
755+
}
756+
expectedOwner1 := expectedFee(tx1, receipts[0], header)
757+
expectedOwner2 := expectedFee(tx2, receipts[1], header)
758+
if expectCurrentOwner {
759+
if owner1Balance.Cmp(expectedOwner1) != 0 {
760+
t.Fatalf("unexpected first owner fee credit: got %v want %v", owner1Balance, expectedOwner1)
761+
}
762+
if owner2Balance.Cmp(expectedOwner2) != 0 {
763+
t.Fatalf("unexpected updated owner fee credit: got %v want %v", owner2Balance, expectedOwner2)
764+
}
765+
} else {
766+
expectedOriginalOwner := new(big.Int).Add(expectedOwner1, expectedOwner2)
767+
if owner1Balance.Cmp(expectedOriginalOwner) != 0 {
768+
t.Fatalf("unexpected pre-Prague fee credit on original owner: got %v want %v", owner1Balance, expectedOriginalOwner)
769+
}
770+
if owner2Balance.Sign() != 0 {
771+
t.Fatalf("expected updated owner to receive no fee credit before Prague, have %v", owner2Balance)
772+
}
773+
}
774+
775+
totalCredited := new(big.Int).Add(new(big.Int).Set(owner1Balance), owner2Balance)
776+
expectedTotal := new(big.Int).Add(expectedOwner1, expectedOwner2)
777+
if totalCredited.Cmp(expectedTotal) != 0 {
778+
t.Fatalf("unexpected total fee credit: got %v want %v", totalCredited, expectedTotal)
779+
}
780+
if expectCurrentOwner && owner1Balance.Cmp(expectedTotal) == 0 {
781+
t.Fatalf("all fee credit stayed with original owner, updated owner balance %v", owner2Balance)
782+
}
783+
}
784+
785+
func testApplyTransactionCoinbaseOwnerByFork(t *testing.T, recipient common.Address, tokensFee map[common.Address]*big.Int, pragueBlock *big.Int, expectedFee func(*types.Transaction, *types.Receipt, *types.Header) *big.Int) {
786+
var (
787+
config = &params.ChainConfig{
788+
ChainID: big.NewInt(1),
789+
HomesteadBlock: big.NewInt(0),
790+
EIP150Block: big.NewInt(0),
791+
EIP155Block: big.NewInt(0),
792+
EIP158Block: big.NewInt(0),
793+
ByzantiumBlock: big.NewInt(0),
794+
ConstantinopleBlock: big.NewInt(0),
795+
PetersburgBlock: big.NewInt(0),
796+
IstanbulBlock: big.NewInt(0),
797+
BerlinBlock: big.NewInt(0),
798+
LondonBlock: big.NewInt(0),
799+
Eip1559Block: big.NewInt(0),
800+
PragueBlock: new(big.Int).Set(pragueBlock),
801+
Ethash: new(params.EthashConfig),
802+
}
803+
signer = types.LatestSigner(config)
804+
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
805+
sender = crypto.PubkeyToAddress(testKey.PublicKey)
806+
coinbase = common.HexToAddress("0x00000000000000000000000000000000000000c0")
807+
owner1 = common.HexToAddress("0x00000000000000000000000000000000000000a1")
808+
owner2 = common.HexToAddress("0x00000000000000000000000000000000000000a2")
809+
blockNumber = new(big.Int).Add(common.TIPTRC21Fee, common.Big1)
810+
gasTipCap = new(big.Int).Set(common.BaseFee)
811+
gasFeeCap = new(big.Int).Mul(new(big.Int).Set(common.BaseFee), big.NewInt(2))
812+
)
813+
814+
db := rawdb.NewMemoryDatabase()
815+
gspec := &Genesis{
816+
Config: config,
817+
Alloc: types.GenesisAlloc{
818+
sender: {
819+
Balance: big.NewInt(1000000000000000000),
820+
Nonce: 0,
821+
},
822+
},
823+
}
824+
genesis := gspec.MustCommit(db)
825+
blockchain, _ := NewBlockChain(db, nil, gspec, ethash.NewFaker(), vm.Config{})
826+
defer blockchain.Stop()
827+
828+
statedb, err := blockchain.State()
829+
if err != nil {
830+
t.Fatalf("Failed to get state: %v", err)
831+
}
832+
setCoinbaseOwner(statedb, coinbase, owner1)
833+
834+
tx1, err := types.SignTx(types.NewTx(&types.DynamicFeeTx{
835+
Nonce: 0,
836+
GasTipCap: gasTipCap,
837+
GasFeeCap: gasFeeCap,
838+
Gas: 21000,
839+
To: &recipient,
840+
Value: big.NewInt(0),
841+
}), signer, testKey)
842+
if err != nil {
843+
t.Fatalf("Failed to sign first tx: %v", err)
844+
}
845+
tx2, err := types.SignTx(types.NewTx(&types.DynamicFeeTx{
846+
Nonce: 1,
847+
GasTipCap: gasTipCap,
848+
GasFeeCap: gasFeeCap,
849+
Gas: 21068,
850+
To: &recipient,
851+
Value: big.NewInt(0),
852+
Data: []byte{0x01},
853+
}), signer, testKey)
854+
if err != nil {
855+
t.Fatalf("Failed to sign second tx: %v", err)
856+
}
857+
858+
header := &types.Header{
859+
ParentHash: genesis.Hash(),
860+
Coinbase: coinbase,
861+
Difficulty: big.NewInt(0),
862+
GasLimit: 1000000,
863+
Number: blockNumber,
864+
Time: genesis.Time() + 10,
865+
UncleHash: types.EmptyUncleHash,
866+
BaseFee: new(big.Int).Set(common.BaseFee),
867+
}
868+
vmContext := NewEVMBlockContext(header, blockchain, nil)
869+
evm := vm.NewEVM(vmContext, statedb, nil, config, vm.Config{})
870+
gasPool := new(GasPool).AddGas(header.GasLimit)
871+
872+
applyTx := func(index int, tx *types.Transaction) *types.Receipt {
873+
var balanceFee *big.Int
874+
if tx.To() != nil {
875+
if value, ok := tokensFee[*tx.To()]; ok {
876+
balanceFee = value
877+
}
878+
}
879+
msg, err := TransactionToMessage(tx, signer, balanceFee, header.Number, header.BaseFee)
880+
if err != nil {
881+
t.Fatalf("Failed to build tx message: %v", err)
882+
}
883+
statedb.SetTxContext(tx.Hash(), index)
884+
var usedGas uint64
885+
receipt, _, _, err := ApplyTransactionWithEVM(msg, gasPool, statedb, header.Number, header.Hash(), tx, &usedGas, evm, balanceFee, common.Address{})
886+
if err != nil {
887+
t.Fatalf("ApplyTransactionWithEVM failed: %v", err)
888+
}
889+
return receipt
890+
}
891+
892+
receipt1 := applyTx(0, tx1)
893+
setCoinbaseOwner(statedb, coinbase, owner2)
894+
receipt2 := applyTx(1, tx2)
895+
896+
owner1Balance := statedb.GetBalance(owner1)
897+
owner2Balance := statedb.GetBalance(owner2)
898+
if owner1Balance.Sign() <= 0 {
899+
t.Fatalf("expected initial owner to receive first tx fee credit, have %v", owner1Balance)
900+
}
901+
if owner2Balance.Sign() <= 0 {
902+
t.Fatalf("expected updated owner to receive second tx fee credit, have %v", owner2Balance)
903+
}
904+
expectedOwner1 := expectedFee(tx1, receipt1, header)
905+
expectedOwner2 := expectedFee(tx2, receipt2, header)
906+
if owner1Balance.Cmp(expectedOwner1) != 0 {
907+
t.Fatalf("unexpected first owner fee credit: got %v want %v", owner1Balance, expectedOwner1)
908+
}
909+
if owner2Balance.Cmp(expectedOwner2) != 0 {
910+
t.Fatalf("unexpected updated owner fee credit: got %v want %v", owner2Balance, expectedOwner2)
911+
}
912+
}
913+
914+
func setCoinbaseOwner(statedb *state.StateDB, coinbase common.Address, owner common.Address) {
915+
ownerSlot := state.GetLocMappingAtKey(coinbase.Hash(), 1)
916+
statedb.SetState(common.MasternodeVotingSMCBinary, common.BigToHash(ownerSlot), owner.Hash())
917+
}
918+
919+
type noOpFinalizeEngine struct {
920+
consensus.Engine
921+
}
922+
923+
func (e noOpFinalizeEngine) Finalize(chain consensus.ChainReader, header *types.Header, state vm.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
924+
return nil, nil
925+
}
926+
601927
func TestProcessParentBlockHash(t *testing.T) {
602928
var (
603929
chainConfig = params.MergedTestChainConfig

0 commit comments

Comments
 (0)