From 0957146fcb89a356a912a79ae237f0341b1af58d Mon Sep 17 00:00:00 2001 From: yumiel yoomee1313 Date: Thu, 6 Aug 2026 16:37:42 +0800 Subject: [PATCH] blockchain: isolate tx cache during prefetch --- blockchain/state_prefetcher.go | 8 +++ blockchain/state_prefetcher_test.go | 93 +++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 blockchain/state_prefetcher_test.go diff --git a/blockchain/state_prefetcher.go b/blockchain/state_prefetcher.go index ec271f074..f7fac8844 100644 --- a/blockchain/state_prefetcher.go +++ b/blockchain/state_prefetcher.go @@ -58,6 +58,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, stateDB *state.StateDB, c } // Block precaching permitted to continue, execute the transaction stateDB.SetTxContext(tx.Hash(), block.Hash(), i) + tx = copyTxForPrefetch(tx) if err := precacheTransaction(p.chain, nil, stateDB, header, tx, cfg); err != nil { return // Ugh, something went horribly wrong, bail out } @@ -81,11 +82,18 @@ func (p *statePrefetcher) PrefetchTx(block *types.Block, ti int, stateDB *state. // Block precaching permitted to continue, execute the transaction stateDB.SetTxContext(tx.Hash(), block.Hash(), ti) + tx = copyTxForPrefetch(tx) if err := precacheTransaction(p.chain, nil, stateDB, header, tx, cfg); err != nil { return // Ugh, something went horribly wrong, bail out } } +// Prefetch runs against speculative state, so it must not overwrite the +// state-dependent execution caches on the transaction imported by the block. +func copyTxForPrefetch(tx *types.Transaction) *types.Transaction { + return types.NewTx(tx.GetTxInternalData()) +} + // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. diff --git a/blockchain/state_prefetcher_test.go b/blockchain/state_prefetcher_test.go new file mode 100644 index 000000000..9f87c0118 --- /dev/null +++ b/blockchain/state_prefetcher_test.go @@ -0,0 +1,93 @@ +// Copyright 2026 The Kaia Authors +// This file is part of the Kaia library. +// +// The Kaia 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 Kaia 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 Kaia library. If not, see . + +package blockchain + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "github.com/kaiachain/kaia/blockchain/types" + "github.com/kaiachain/kaia/blockchain/types/accountkey" + "github.com/kaiachain/kaia/common" + "github.com/kaiachain/kaia/crypto" + "github.com/kaiachain/kaia/fork" + "github.com/kaiachain/kaia/params" + "github.com/stretchr/testify/require" +) + +type prefetchTestKeyPicker map[common.Address]accountkey.AccountKey + +func (p prefetchTestKeyPicker) GetKey(addr common.Address) accountkey.AccountKey { + return p[addr] +} + +func (p prefetchTestKeyPicker) Exist(addr common.Address) bool { + return p[addr] != nil +} + +// TestCopyTxForPrefetchDoesNotOverwriteOriginalValidatedGas verifies that +// prefetch cannot overwrite state-dependent validatedGas cached on the original +// block transaction. +func TestCopyTxForPrefetchDoesNotOverwriteOriginalValidatedGas(t *testing.T) { + require.NoError(t, fork.SetHardForkBlockNumberConfig(¶ms.ChainConfig{ + IstanbulCompatibleBlock: big.NewInt(100), + })) + + const blockNumber = uint64(0) + signer := types.LatestSignerForChainID(big.NewInt(1)) + senderKey, err := crypto.GenerateKey() + require.NoError(t, err) + extraKey, err := crypto.GenerateKey() + require.NoError(t, err) + + from := crypto.PubkeyToAddress(senderKey.PublicKey) + to := common.HexToAddress("0x0000000000000000000000000000000000000100") + txData, err := types.NewTxInternalDataWithMap(types.TxTypeValueTransfer, map[types.TxValueKeyType]interface{}{ + types.TxValueKeyNonce: uint64(0), + types.TxValueKeyTo: to, + types.TxValueKeyAmount: big.NewInt(1), + types.TxValueKeyGasLimit: uint64(100000), + types.TxValueKeyGasPrice: big.NewInt(1), + types.TxValueKeyFrom: from, + }) + require.NoError(t, err) + + tx := types.NewTx(txData) + require.NoError(t, tx.SignWithKeys(signer, []*ecdsa.PrivateKey{senderKey})) + + stalePicker := prefetchTestKeyPicker{ + from: accountkey.NewAccountKeyPublicWithValue(&senderKey.PublicKey), + } + currentPicker := prefetchTestKeyPicker{ + from: accountkey.NewAccountKeyWeightedMultiSigWithValues(1, accountkey.WeightedPublicKeys{ + accountkey.NewWeightedPublicKey(1, (*accountkey.PublicKeySerializable)(&senderKey.PublicKey)), + accountkey.NewWeightedPublicKey(1, (*accountkey.PublicKeySerializable)(&extraKey.PublicKey)), + }), + } + + _, err = tx.AsMessageWithAccountKeyPicker(signer, currentPicker, blockNumber) + require.NoError(t, err) + originalGas := tx.ValidatedGas().IntrinsicGas + + prefetchTx := copyTxForPrefetch(tx) + _, err = prefetchTx.AsMessageWithAccountKeyPicker(signer, stalePicker, blockNumber) + require.NoError(t, err) + + require.Equal(t, params.TxValidationGasPerKey, originalGas-prefetchTx.ValidatedGas().IntrinsicGas) + require.Equal(t, originalGas, tx.ValidatedGas().IntrinsicGas) +}