mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
Fixed wrong assumption about SelfDestruct having only at max one withdraw balance change
It appears a self destruct can really lead to 3 balance changes, 2 of which are withdraw of the money if the contract being self destructed and the self destruct beneficiary are the same. We thought it was a bug in the Geth tracing API but it appears it's not and we must actually correctly handle that when fixing up balance changes. So we now support the case where there is 2 withdraws, in this case, the old Firehose 2.x was only ever keeping the last one, so we can simply filter it out of the changes to retain backward compatibility.
This commit is contained in:
parent
bbcf323c96
commit
d444c54548
2 changed files with 86 additions and 40 deletions
|
|
@ -246,10 +246,9 @@ func (s *hookedStateDB) SelfDestruct6780(address common.Address) (uint256.Int, b
|
||||||
|
|
||||||
prev, changed := s.inner.SelfDestruct6780(address)
|
prev, changed := s.inner.SelfDestruct6780(address)
|
||||||
|
|
||||||
// No balance change hook for EIP-6780 as the instruction handler does it already
|
if s.hooks.OnBalanceChange != nil && changed && !prev.IsZero() {
|
||||||
// if s.hooks.OnBalanceChange != nil && changed && !prev.IsZero() {
|
s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
|
||||||
// s.hooks.OnBalanceChange(address, prev.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestruct)
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if s.hooks.OnCodeChange != nil && changed && len(prevCode) > 0 {
|
if s.hooks.OnCodeChange != nil && changed && len(prevCode) > 0 {
|
||||||
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
|
s.hooks.OnCodeChange(address, prevCodeHash, prevCode, types.EmptyCodeHash, nil)
|
||||||
|
|
|
||||||
|
|
@ -895,7 +895,7 @@ func (f *Firehose) OnCallEnter(depth int, typ byte, from common.Address, to comm
|
||||||
// So if we are in compatibility mode and the block is Cancun, we must reorder the balance changes
|
// So if we are in compatibility mode and the block is Cancun, we must reorder the balance changes
|
||||||
// to match the Firehose 2.3 behavior.
|
// to match the Firehose 2.3 behavior.
|
||||||
if *f.applyBackwardCompatibility && f.blockRules.IsCancun {
|
if *f.applyBackwardCompatibility && f.blockRules.IsCancun {
|
||||||
f.maybeReorderSelfDestructBalanceChanges()
|
f.fixSelfDestructBalanceChanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
firehoseDebug("ignoring OnCallEnter for SELFDESTRUCT opcode, not recorded as a call")
|
firehoseDebug("ignoring OnCallEnter for SELFDESTRUCT opcode, not recorded as a call")
|
||||||
|
|
@ -914,57 +914,104 @@ func (f *Firehose) OnCallEnter(depth int, typ byte, from common.Address, to comm
|
||||||
f.callStart(computeCallSource(depth), callType, from, to, input, gas, value)
|
f.callStart(computeCallSource(depth), callType, from, to, input, gas, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firehose) maybeReorderSelfDestructBalanceChanges() {
|
func (f *Firehose) fixSelfDestructBalanceChanges() {
|
||||||
type indexedBalanceChange struct {
|
|
||||||
index int
|
|
||||||
ordinal uint64
|
|
||||||
change *pbeth.BalanceChange
|
|
||||||
}
|
|
||||||
|
|
||||||
f.ensureInCall()
|
f.ensureInCall()
|
||||||
activeCall := f.callStack.Peek()
|
activeCall := f.callStack.Peek()
|
||||||
|
|
||||||
var increaseBalanceChange indexedBalanceChange
|
if len(activeCall.BalanceChanges) == 0 {
|
||||||
var decreaseBalanceChange indexedBalanceChange
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for index, change := range activeCall.BalanceChanges {
|
// It's possible in the new tracing API to get 3 balance changes for a self destruct if
|
||||||
switch change.Reason {
|
// the self destruct beneficiary is the same as the contract and if the contract was
|
||||||
case pbeth.BalanceChange_REASON_SUICIDE_WITHDRAW:
|
// created and destructed in the same transaction.
|
||||||
if decreaseBalanceChange.change != nil {
|
//
|
||||||
f.panicInvalidState(fmt.Sprintf("more than one balance change with reason REASON_SUICIDE_WITHDRAW found in call #%d, this is not expected", activeCall.Index), 0)
|
// In this case, we get first a decrease from contract to 0, then an increase from 0 to beneficiary
|
||||||
}
|
// (which is the contract) and finally a decrease from contract to 0 again.
|
||||||
|
//
|
||||||
|
// In the Firehose 2.3 model, this wasn't recorded properly. The first decrease was always ignored, and
|
||||||
|
// only the second one was recorded.
|
||||||
|
|
||||||
decreaseBalanceChange.index = index
|
withdrawIndices := make([]int, 0, 2)
|
||||||
decreaseBalanceChange.ordinal = change.Ordinal
|
refundIndices := make([]int, 0, 1)
|
||||||
decreaseBalanceChange.change = change
|
for i, change := range activeCall.BalanceChanges {
|
||||||
|
if change.Reason == pbeth.BalanceChange_REASON_SUICIDE_WITHDRAW {
|
||||||
case pbeth.BalanceChange_REASON_SUICIDE_REFUND:
|
withdrawIndices = append(withdrawIndices, i)
|
||||||
if increaseBalanceChange.change != nil {
|
} else if change.Reason == pbeth.BalanceChange_REASON_SUICIDE_REFUND {
|
||||||
f.panicInvalidState(fmt.Sprintf("more than one balance change with reason REASON_SUICIDE_REFUND found in call #%d, this is not expected", activeCall.Index), 0)
|
refundIndices = append(refundIndices, i)
|
||||||
}
|
|
||||||
|
|
||||||
increaseBalanceChange.index = index
|
|
||||||
increaseBalanceChange.ordinal = change.Ordinal
|
|
||||||
increaseBalanceChange.change = change
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing to do if there is only one side
|
// No suicide balance change found, nothing to do
|
||||||
if decreaseBalanceChange.change == nil || increaseBalanceChange.change == nil {
|
if len(withdrawIndices) == 0 && len(refundIndices) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Both side cannot be 0 (due to above condition), if only one side is 0, there is also
|
||||||
|
// nothing todo.
|
||||||
|
if len(withdrawIndices) == 0 || len(refundIndices) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(refundIndices) == 1 && len(withdrawIndices) == 1 {
|
||||||
|
f.invertWithdrawAndRefundBalanceChange(activeCall, withdrawIndices[0], refundIndices[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(refundIndices) == 1 && len(withdrawIndices) == 2 {
|
||||||
|
f.removeFirstWithdrawBalanceChange(activeCall, withdrawIndices[1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.panicInvalidState(fmt.Sprintf("invalid state when fixing self destruct balance changes found in call #%d, withdraw indices %v and refund indices %v not matching one of the expected case(s)", activeCall.Index, withdrawIndices, refundIndices), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehose) invertWithdrawAndRefundBalanceChange(activeCall *pbeth.Call, withdrawIndex int, refundIndex int) {
|
||||||
// Nothing to do if they are already ordered according to Firehose 2.3 rules
|
// Nothing to do if they are already ordered according to Firehose 2.3 rules
|
||||||
if decreaseBalanceChange.index > increaseBalanceChange.index {
|
if withdrawIndex > refundIndex {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, invert them and their ordinal
|
// Otherwise, invert them to fit Firehose 2.3 rules
|
||||||
decreaseBalanceChange.change.Ordinal = increaseBalanceChange.ordinal
|
changes := activeCall.BalanceChanges
|
||||||
increaseBalanceChange.change.Ordinal = decreaseBalanceChange.ordinal
|
withdrawOrdinal := changes[withdrawIndex].Ordinal
|
||||||
|
refundOrdinal := changes[refundIndex].Ordinal
|
||||||
|
|
||||||
activeCall.BalanceChanges[decreaseBalanceChange.index] = increaseBalanceChange.change
|
changes[withdrawIndex].Ordinal = refundOrdinal
|
||||||
activeCall.BalanceChanges[increaseBalanceChange.index] = decreaseBalanceChange.change
|
changes[refundIndex].Ordinal = withdrawOrdinal
|
||||||
|
|
||||||
|
withdrawChange := changes[withdrawIndex]
|
||||||
|
changes[withdrawIndex] = changes[refundIndex]
|
||||||
|
changes[refundIndex] = withdrawChange
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firehose) removeFirstWithdrawBalanceChange(activeCall *pbeth.Call, lastWithdrawIndex int) {
|
||||||
|
finalChanges := make([]*pbeth.BalanceChange, 0, len(activeCall.BalanceChanges)-1)
|
||||||
|
for i, change := range activeCall.BalanceChanges {
|
||||||
|
switch change.Reason {
|
||||||
|
case pbeth.BalanceChange_REASON_SUICIDE_WITHDRAW:
|
||||||
|
if i != lastWithdrawIndex {
|
||||||
|
// Skip all except the last withdraw change.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We remove the first one, this one must be shifted by one
|
||||||
|
change.Ordinal -= 1
|
||||||
|
|
||||||
|
case pbeth.BalanceChange_REASON_SUICIDE_REFUND:
|
||||||
|
// We remove the first one withdraw, which always happens before the refund,
|
||||||
|
// this one must be shifted by one
|
||||||
|
change.Ordinal -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
finalChanges = append(finalChanges, change)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We remove one change, we must adjust the overall block ordinal to fit
|
||||||
|
f.blockOrdinal.value -= 1
|
||||||
|
activeCall.BalanceChanges = finalChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnCallExit is called after the call finishes to finalize the tracing.
|
// OnCallExit is called after the call finishes to finalize the tracing.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue