core, plugins: add exex reorg support and access to receipts

This commit is contained in:
Péter Szilágyi 2024-10-16 20:32:38 +03:00
parent c4427192cb
commit 22d0842323
11 changed files with 134 additions and 7 deletions

View file

@ -1565,8 +1565,8 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
// canonical blocks. Avoid firing too many ChainHeadEvents, // canonical blocks. Avoid firing too many ChainHeadEvents,
// we will fire an accumulated ChainHeadEvent and disable fire // we will fire an accumulated ChainHeadEvent and disable fire
// event here. // event here.
if emitHeadEvent {
exex.TriggerHeadHook(block.Header()) exex.TriggerHeadHook(block.Header())
if emitHeadEvent {
bc.chainHeadFeed.Send(ChainHeadEvent{Header: block.Header()}) bc.chainHeadFeed.Send(ChainHeadEvent{Header: block.Header()})
} }
return CanonStatTy, nil return CanonStatTy, nil
@ -1632,7 +1632,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
// Fire a single chain head event if we've progressed the chain // Fire a single chain head event if we've progressed the chain
defer func() { defer func() {
if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() {
exex.TriggerHeadHook(lastCanon.Header())
bc.chainHeadFeed.Send(ChainHeadEvent{Header: lastCanon.Header()}) bc.chainHeadFeed.Send(ChainHeadEvent{Header: lastCanon.Header()})
} }
}() }()
@ -2262,6 +2261,10 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
// rewind the canonical chain to a lower point. // rewind the canonical chain to a lower point.
log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain), "newnum", newHead.Number, "newhash", newHead.Hash(), "newblocks", len(newChain)) log.Error("Impossible reorg, please file an issue", "oldnum", oldHead.Number, "oldhash", oldHead.Hash(), "oldblocks", len(oldChain), "newnum", newHead.Number, "newhash", newHead.Hash(), "newblocks", len(newChain))
} }
// Trigger revertal reorgs
if len(oldChain) > 0 {
exex.TriggerReorgHook(oldChain, true)
}
// Acquire the tx-lookup lock before mutation. This step is essential // Acquire the tx-lookup lock before mutation. This step is essential
// as the txlookups should be changed atomically, and all subsequent // as the txlookups should be changed atomically, and all subsequent
// reads should be blocked until the mutation is complete. // reads should be blocked until the mutation is complete.
@ -2369,6 +2372,12 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
// Release the tx-lookup lock after mutation. // Release the tx-lookup lock after mutation.
bc.txLookupLock.Unlock() bc.txLookupLock.Unlock()
// Trigger application reorgs
if len(newChain) > 1 {
slices.Reverse(newChain)
exex.TriggerReorgHook(newChain[:len(newChain)-1], false)
slices.Reverse(newChain)
}
return nil return nil
} }

View file

@ -209,6 +209,27 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
return return
} }
// GetReceiptsByNumber retrieves the receipts for all transactions in a given block.
func (bc *BlockChain) GetReceiptsByNumber(number uint64) types.Receipts {
hash := rawdb.ReadCanonicalHash(bc.db, number)
if hash == (common.Hash{}) {
return nil
}
if receipts, ok := bc.receiptsCache.Get(hash); ok {
return receipts
}
header := bc.GetHeader(hash, number)
if header == nil {
return nil
}
receipts := rawdb.ReadReceipts(bc.db, hash, number, header.Time, bc.chainConfig)
if receipts == nil {
return nil
}
bc.receiptsCache.Add(hash, receipts)
return receipts
}
// GetReceiptsByHash retrieves the receipts for all transactions in a given block. // GetReceiptsByHash retrieves the receipts for all transactions in a given block.
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
if receipts, ok := bc.receiptsCache.Get(hash); ok { if receipts, ok := bc.receiptsCache.Get(hash); ok {

View file

@ -47,4 +47,5 @@ type PluginV1 struct {
OnInit InitHook // Called when the chain gets initialized within Geth OnInit InitHook // Called when the chain gets initialized within Geth
OnClose CloseHook // Called when the chain gets torn down within Geth OnClose CloseHook // Called when the chain gets torn down within Geth
OnHead HeadHook // Called when the chain head block is updated in Geth OnHead HeadHook // Called when the chain head block is updated in Geth
OnReorg ReorgHook // Called wnen the chain reorgs to a sidechain within Geth
} }

View file

@ -32,6 +32,7 @@ type gethChain interface {
GetHeaderByNumber(number uint64) *types.Header GetHeaderByNumber(number uint64) *types.Header
GetBlockByNumber(number uint64) *types.Block GetBlockByNumber(number uint64) *types.Block
StateAt(root common.Hash) (*state.StateDB, error) StateAt(root common.Hash) (*state.StateDB, error)
GetReceiptsByNumber(number uint64) types.Receipts
} }
// chainAdapter is an adapter to convert Geth's internal blockchain (unstable // chainAdapter is an adapter to convert Geth's internal blockchain (unstable
@ -76,3 +77,17 @@ func (a *chainAdapter) State(root common.Hash) exex.State {
} }
return wrapState(state) return wrapState(state)
} }
// Receipts retrieves a set of receits belonging to all transactions within
// a block from the canonical chain. Receipts on side-chains are not exposed
// by the Chain interface.
func (a *chainAdapter) Receipts(number uint64) []*types.Receipt {
// Receipts have public fields, copy to prevent modification
receipts := a.chain.GetReceiptsByNumber(number)
copies := make([]*types.Receipt, 0, len(receipts))
for _, receipt := range receipts {
copies = append(copies, types.CopyReceipt(receipt))
}
return copies
}

View file

@ -38,6 +38,7 @@ type registry interface {
TriggerInitHook(chain exex.Chain) TriggerInitHook(chain exex.Chain)
TriggerCloseHook() TriggerCloseHook()
TriggerHeadHook(head *types.Header) TriggerHeadHook(head *types.Header)
TriggerReorgHook(headers []*types.Header, revert bool)
} }
// Plugins returns a list of all registered plugins to generate CLI flags. // Plugins returns a list of all registered plugins to generate CLI flags.
@ -64,3 +65,8 @@ func TriggerCloseHook() {
func TriggerHeadHook(head *types.Header) { func TriggerHeadHook(head *types.Header) {
globalRegistry.TriggerHeadHook(head) globalRegistry.TriggerHeadHook(head)
} }
// TriggerReorgHook triggers the OnReorg hook in exex plugins.
func TriggerReorgHook(headers []*types.Header, revert bool) {
globalRegistry.TriggerReorgHook(headers, revert)
}

View file

@ -31,8 +31,23 @@ type CloseHook = func()
// - In sync, this will be called on fork-choice updates // - In sync, this will be called on fork-choice updates
type HeadHook = func(head *types.Header) type HeadHook = func(head *types.Header)
// TODO(karalabe): This is interesting. We need to keep events in sync with the // ReorgHook is called when the chain head is updated to a different parent than
// user's chain access capabilities. We either need to provide side-chain access, // the previous head. In this case previously applied state changes need to be
// which gets nasty fast; or we need to reorg in lockstep; or we need two events // rolled back, and state changes from a sidechain need to be applied.
// one to start a reorg (going back) and one having finished (going forward). //
// type ReorgHook = func(old, new *types.Header) error // This method is called with a set of header being operated on and the direction
// of the operation, usually both directions being called one after the other:
//
// - If revert == true, the given headers are being rolled back, they are in
// reverse order, headers[0] being the previous chain head, and the last item
// being the olders block getting undone.
// - If revert == false, the given headers are being applied after the rollback,
// they are in forward order, headers[0] being the oldest block being applied
// and the last item being the newest getting applied. Note, the chain head
// that triggered the reorg will arrive in the HeadHook.
//
// The reason the reorg event it "emitted" in two parts is for both operations to
// have access to a unified singletone view of the chain. An alternative would be
// to pass in both the reverted and applied headers at the same time, but that
// would require chain accessorts to support sidechains, which complicate APIs.
type ReorgHook = func(headers []*types.Header, revert bool)

View file

@ -39,4 +39,9 @@ type Chain interface {
// State retrieves a state accessor at a given root hash. // State retrieves a state accessor at a given root hash.
State(root common.Hash) State State(root common.Hash) State
// Receipts retrieves a set of receipts belonging to all transactions within
// a block from the canonical chain. Receipts on side-chains are not exposed
// by the Chain interface.
Receipts(number uint64) []*types.Receipt
} }

View file

@ -78,3 +78,12 @@ func (reg *registry) TriggerHeadHook(head *types.Header) {
} }
} }
} }
// TriggerReorgHook triggers the OnReorg hook in exex plugins.
func (reg *registry) TriggerReorgHook(headers []*types.Header, revert bool) {
for _, plugin := range globalRegistry.pluginsV1 {
if plugin.OnReorg != nil {
plugin.OnReorg(headers, revert)
}
}
}

View file

@ -53,6 +53,20 @@ type Log struct {
Removed bool `json:"removed" rlp:"-"` Removed bool `json:"removed" rlp:"-"`
} }
// CopyLog creates a deep copy of a log.
func CopyLog(l *Log) *Log {
cpy := *l
if len(l.Topics) > 0 {
cpy.Topics = make([]common.Hash, len(l.Topics))
copy(cpy.Topics, l.Topics)
}
if len(l.Data) > 0 {
cpy.Data = make([]byte, len(l.Data))
copy(cpy.Data, l.Data)
}
return &cpy
}
type logMarshaling struct { type logMarshaling struct {
Data hexutil.Bytes Data hexutil.Bytes
BlockNumber hexutil.Uint64 BlockNumber hexutil.Uint64

View file

@ -117,6 +117,31 @@ func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt {
return r return r
} }
// CopyReceipt creates a deep copy of a receipt.
func CopyReceipt(r *Receipt) *Receipt {
cpy := *r
if len(r.PostState) > 0 {
cpy.PostState = make([]byte, len(r.PostState))
copy(cpy.PostState, r.PostState)
}
if len(r.Logs) > 0 {
cpy.Logs = make([]*Log, len(r.Logs))
for i, log := range r.Logs {
cpy.Logs[i] = CopyLog(log)
}
}
if r.EffectiveGasPrice != nil {
cpy.EffectiveGasPrice = new(big.Int).Set(r.EffectiveGasPrice)
}
if r.BlobGasPrice != nil {
cpy.BlobGasPrice = new(big.Int).Set(r.BlobGasPrice)
}
if r.BlockNumber != nil {
cpy.BlockNumber = new(big.Int).Set(r.BlockNumber)
}
return &cpy
}
// EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt // EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt
// into an RLP stream. If no post state is present, byzantium fork is assumed. // into an RLP stream. If no post state is present, byzantium fork is assumed.
func (r *Receipt) EncodeRLP(w io.Writer) error { func (r *Receipt) EncodeRLP(w io.Writer) error {

View file

@ -33,5 +33,12 @@ func newMinimalPlugin(config *exex.ConfigV1) (*exex.PluginV1, error) {
OnHead: func(head *types.Header) { OnHead: func(head *types.Header) {
config.Logger.Info("Chain head updated", "number", head.Number, "hash", head.Hash()) config.Logger.Info("Chain head updated", "number", head.Number, "hash", head.Hash())
}, },
OnReorg: func(headers []*types.Header, revert bool) {
if revert {
config.Logger.Warn("Reorging blocks out", "count", len(headers))
} else {
config.Logger.Warn("Reorging blocks in", "count", len(headers))
}
},
}, nil }, nil
} }