forked from ethereum-optimism/optimism
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request ethereum-optimism#125 from ethereum-optimism/impl-…
…eth-utils ref impl: eth utils - watch head, hash/height id type, RPC source interfaces
- Loading branch information
Showing
6 changed files
with
261 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package eth | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math/big" | ||
) | ||
|
||
// BlockLinkByNumber Retrieves the *currently* canonical block-hash at the given block-height, and the parent before it. | ||
// The results of this should not be cached, or the cache needs to be reorg-aware. | ||
type BlockLinkByNumber interface { | ||
BlockLinkByNumber(ctx context.Context, num uint64) (self BlockID, parent BlockID, err error) | ||
} | ||
|
||
// BlockLinkByNumberFn implements BlockLinkByNumber to implement the interface as anonymous function | ||
type BlockLinkByNumberFn func(ctx context.Context, num uint64) (self BlockID, parent BlockID, err error) | ||
|
||
func (fn BlockLinkByNumberFn) BlockLinkByNumber(ctx context.Context, num uint64) (self BlockID, parent BlockID, err error) { | ||
return fn(ctx, num) | ||
} | ||
|
||
// CanonicalChain presents the block-hashes by height by wrapping a header-source | ||
// (useful due to lack of a direct JSON RPC endpoint) | ||
func CanonicalChain(l1Src HeaderByNumberSource) BlockLinkByNumber { | ||
return BlockLinkByNumberFn(func(ctx context.Context, num uint64) (BlockID, BlockID, error) { | ||
header, err := l1Src.HeaderByNumber(ctx, big.NewInt(int64(num))) | ||
if err != nil { | ||
// w%: wrap the error, we still need to detect if a canonical block is not found, a.k.a. end of chain. | ||
return BlockID{}, BlockID{}, fmt.Errorf("failed to determine block-hash of height %d, could not get header: %w", num, err) | ||
} | ||
parentNum := num | ||
if parentNum > 0 { | ||
parentNum -= 1 | ||
} | ||
return BlockID{Hash: header.Hash(), Number: num}, BlockID{Hash: header.ParentHash, Number: parentNum}, nil | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package eth | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/ethereum/go-ethereum" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/event" | ||
) | ||
|
||
type HeadSignal struct { | ||
Parent BlockID | ||
Self BlockID | ||
} | ||
|
||
// HeadSignalFn is used as callback function to accept head-signals | ||
type HeadSignalFn func(sig HeadSignal) | ||
|
||
// WatchHeadChanges wraps a new-head subscription from NewHeadSource to feed the given Tracker | ||
func WatchHeadChanges(ctx context.Context, src NewHeadSource, fn HeadSignalFn) (ethereum.Subscription, error) { | ||
headChanges := make(chan *types.Header, 10) | ||
sub, err := src.SubscribeNewHead(ctx, headChanges) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return event.NewSubscription(func(quit <-chan struct{}) error { | ||
defer sub.Unsubscribe() | ||
for { | ||
select { | ||
case header := <-headChanges: | ||
hash := header.Hash() | ||
height := header.Number.Uint64() | ||
self := BlockID{Hash: hash, Number: height} | ||
parent := BlockID{} | ||
if height > 0 { | ||
parent = BlockID{Hash: header.ParentHash, Number: height - 1} | ||
} | ||
fn(HeadSignal{Parent: parent, Self: self}) | ||
case err := <-sub.Err(): | ||
return err | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
case <-quit: | ||
return nil | ||
} | ||
} | ||
}), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package eth | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
type BlockID struct { | ||
Hash common.Hash | ||
Number uint64 | ||
} | ||
|
||
func (id BlockID) String() string { | ||
return fmt.Sprintf("%s:%d", id.Hash.String(), id.Number) | ||
} | ||
|
||
// TerminalString implements log.TerminalStringer, formatting a string for console | ||
// output during logging. | ||
func (id BlockID) TerminalString() string { | ||
return fmt.Sprintf("%s:%d", id.Hash.TerminalString(), id.Number) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package eth | ||
|
||
import ( | ||
"context" | ||
"math/big" | ||
"sync/atomic" | ||
|
||
"github.com/ethereum/go-ethereum" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
) | ||
|
||
// Source interfaces isolate individual ethereum.ChainReader methods, | ||
// and enable anonymous functions to implement them. | ||
|
||
type NewHeadSource interface { | ||
SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) | ||
} | ||
|
||
type HeaderByHashSource interface { | ||
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) | ||
} | ||
|
||
type HeaderByNumberSource interface { | ||
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) | ||
} | ||
|
||
type ReceiptSource interface { | ||
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) | ||
} | ||
|
||
type BlockByHashSource interface { | ||
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) | ||
} | ||
|
||
type BlockByNumberSource interface { | ||
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) | ||
} | ||
|
||
type L1Source interface { | ||
NewHeadSource | ||
HeaderByHashSource | ||
HeaderByNumberSource | ||
ReceiptSource | ||
BlockByHashSource | ||
Close() | ||
} | ||
|
||
type BlockSource interface { | ||
BlockByHashSource | ||
BlockByNumberSource | ||
} | ||
|
||
// For test instances, composition etc. we implement the interfaces with equivalent function types | ||
|
||
type NewHeadFn func(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) | ||
|
||
func (fn NewHeadFn) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { | ||
return fn(ctx, ch) | ||
} | ||
|
||
type HeaderByHashFn func(ctx context.Context, hash common.Hash) (*types.Header, error) | ||
|
||
func (fn HeaderByHashFn) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { | ||
return fn(ctx, hash) | ||
} | ||
|
||
type HeaderByNumberFn func(ctx context.Context, number *big.Int) (*types.Header, error) | ||
|
||
func (fn HeaderByNumberFn) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { | ||
return fn(ctx, number) | ||
} | ||
|
||
type ReceiptFn func(ctx context.Context, txHash common.Hash) (*types.Receipt, error) | ||
|
||
func (fn ReceiptFn) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { | ||
return fn(ctx, txHash) | ||
} | ||
|
||
type BlockByHashFn func(ctx context.Context, hash common.Hash) (*types.Block, error) | ||
|
||
func (fn BlockByHashFn) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { | ||
return fn(ctx, hash) | ||
} | ||
|
||
type BlockByNumFn func(ctx context.Context, number *big.Int) (*types.Block, error) | ||
|
||
func (fn BlockByNumFn) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { | ||
return fn(ctx, number) | ||
} | ||
|
||
// CombinedL1Source implements round-robin between multiple L1 sources, | ||
// to divide concurrent requests to multiple endpoints | ||
type CombinedL1Source struct { | ||
i uint32 // track the last used source | ||
sources []L1Source | ||
} | ||
|
||
func NewCombinedL1Source(sources []L1Source) L1Source { | ||
if len(sources) == 0 { | ||
panic("need at least 1 source") | ||
} | ||
return &CombinedL1Source{i: 0, sources: sources} | ||
} | ||
|
||
func (cs *CombinedL1Source) nextSource() L1Source { | ||
return cs.sources[atomic.AddUint32(&cs.i, 1)%uint32(len(cs.sources))] | ||
} | ||
|
||
func (cs *CombinedL1Source) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { | ||
return cs.nextSource().HeaderByHash(ctx, hash) | ||
} | ||
|
||
func (cs *CombinedL1Source) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { | ||
return cs.nextSource().HeaderByNumber(ctx, number) | ||
} | ||
|
||
func (cs *CombinedL1Source) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { | ||
// TODO: can't use multiple sources as consensus, or head may be conflicting too much | ||
return cs.sources[0].SubscribeNewHead(ctx, ch) | ||
} | ||
|
||
func (cs *CombinedL1Source) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { | ||
return cs.nextSource().TransactionReceipt(ctx, txHash) | ||
} | ||
|
||
func (cs *CombinedL1Source) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { | ||
return cs.nextSource().BlockByHash(ctx, hash) | ||
} | ||
|
||
func (cs *CombinedL1Source) Close() { | ||
for _, src := range cs.sources { | ||
src.Close() | ||
} | ||
} |