mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-03-11 21:59:05 +00:00
core/tracing: fix nonce revert edge case (#33978)
We got a report for a bug in the tracing journal which has the responsibility to emit events for all state that must be reverted. The edge case is as follows: on CREATE operations the nonce is incremented. When a create frame reverts, the nonce increment associated with it does **not** revert. This works fine on master. Now one step further: if the parent frame reverts tho, the nonce **should** revert and there is the bug.
This commit is contained in:
parent
91cec92bf3
commit
aa417b03a6
2 changed files with 48 additions and 4 deletions
|
|
@ -155,10 +155,18 @@ func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reaso
|
|||
}
|
||||
|
||||
func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason) {
|
||||
// When a contract is created, the nonce of the creator is incremented.
|
||||
// This change is not reverted when the creation fails.
|
||||
if reason != NonceChangeContractCreator {
|
||||
j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
|
||||
j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
|
||||
if reason == NonceChangeContractCreator {
|
||||
// When a contract is created via CREATE/CREATE2, the creator's nonce is
|
||||
// incremented. The EVM does not revert this when the CREATE frame itself
|
||||
// fails (the nonce change happens before the EVM snapshot). However, if
|
||||
// a parent frame reverts, the nonce must be reverted along with everything
|
||||
// else.
|
||||
//
|
||||
// To achieve this, advance the current frame's revision point past this
|
||||
// entry. The CREATE frame's revert won't touch it (it's below the revision),
|
||||
// but a parent frame's revert will (it's above the parent's revision).
|
||||
j.revisions[len(j.revisions)-1] = len(j.entries)
|
||||
}
|
||||
if j.hooks.OnNonceChangeV2 != nil {
|
||||
j.hooks.OnNonceChangeV2(addr, prev, new, reason)
|
||||
|
|
|
|||
|
|
@ -219,6 +219,42 @@ func TestNonceIncOnCreate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestNonceIncOnCreateParentReverts checks that the creator's nonce increment
|
||||
// from CREATE survives the CREATE frame's own revert but is properly reverted
|
||||
// when the parent call frame reverts.
|
||||
func TestNonceIncOnCreateParentReverts(t *testing.T) {
|
||||
const opCREATE = 0xf0
|
||||
|
||||
tr := &testTracer{t: t}
|
||||
wr, err := WrapWithJournal(&Hooks{OnNonceChange: tr.OnNonceChange})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to wrap test tracer: %v", err)
|
||||
}
|
||||
|
||||
addr := common.HexToAddress("0x1234")
|
||||
{
|
||||
// Parent call frame
|
||||
wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0))
|
||||
{
|
||||
// CREATE frame — creator nonce incremented, then CREATE reverts
|
||||
wr.OnEnter(1, opCREATE, addr, addr, nil, 1000, big.NewInt(0))
|
||||
wr.OnNonceChangeV2(addr, 0, 1, NonceChangeContractCreator)
|
||||
wr.OnExit(1, nil, 100, errors.New("revert"), true)
|
||||
}
|
||||
// After CREATE reverts, nonce should still be 1
|
||||
if tr.nonce != 1 {
|
||||
t.Fatalf("nonce after CREATE revert: got %v, want 1", tr.nonce)
|
||||
}
|
||||
// Parent frame also reverts
|
||||
wr.OnExit(0, nil, 150, errors.New("revert"), true)
|
||||
}
|
||||
|
||||
// After parent reverts, nonce should be back to 0
|
||||
if tr.nonce != 0 {
|
||||
t.Fatalf("nonce after parent revert: got %v, want 0", tr.nonce)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnNonceChangeV2(t *testing.T) {
|
||||
tr := &testTracer{t: t}
|
||||
wr, err := WrapWithJournal(&Hooks{OnNonceChangeV2: tr.OnNonceChangeV2})
|
||||
|
|
|
|||
Loading…
Reference in a new issue