-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mempool Perf fix #1752
Mempool Perf fix #1752
Changes from 6 commits
7327bb0
171fdc8
aa57c0c
0098052
352ffba
5538ec5
94e603c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package common | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/eko/gocache/lib/v4/cache" | ||
gethlog "github.com/ethereum/go-ethereum/log" | ||
"github.com/ten-protocol/go-ten/go/common/log" | ||
) | ||
|
||
// GetCachedValue - returns the cached value for the provided key. If the key is not found, then invoke the 'onFailed' function | ||
// which returns the value, and cache it | ||
func GetCachedValue[V any](cache *cache.Cache[V], logger gethlog.Logger, key any, onFailed func(any) (V, error)) (V, error) { | ||
value, err := cache.Get(context.Background(), key) | ||
if err != nil { | ||
// todo metrics for cache misses | ||
b, err := onFailed(key) | ||
if err != nil { | ||
return b, err | ||
} | ||
CacheValue(cache, logger, key, b) | ||
return b, err | ||
} | ||
|
||
return value, err | ||
} | ||
|
||
func CacheValue[V any](cache *cache.Cache[V], logger gethlog.Logger, key any, v V) { | ||
err := cache.Set(context.Background(), key, v) | ||
if err != nil { | ||
logger.Error("Could not store value in cache", log.ErrKey, err) | ||
} | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,19 @@ import ( | |
"fmt" | ||
"math/big" | ||
"strings" | ||
"sync/atomic" | ||
"time" | ||
"unsafe" | ||
|
||
"github.com/dgraph-io/ristretto" | ||
"github.com/eko/gocache/lib/v4/cache" | ||
ristretto_store "github.com/eko/gocache/store/ristretto/v4" | ||
|
||
gethlog "github.com/ethereum/go-ethereum/log" | ||
"github.com/ten-protocol/go-ten/go/common/log" | ||
"github.com/ten-protocol/go-ten/go/enclave/storage" | ||
|
||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/ethereum/go-ethereum/trie" | ||
"github.com/ten-protocol/go-ten/go/common" | ||
"github.com/ten-protocol/go-ten/go/enclave/core" | ||
"github.com/ten-protocol/go-ten/go/enclave/crypto" | ||
|
@@ -33,6 +43,39 @@ const ( | |
callFieldMaxPriorityFeePerGas = "maxpriorityfeepergas" | ||
) | ||
|
||
// EncodingService handles conversion to Geth data structures | ||
type EncodingService interface { | ||
CreateEthHeaderForBatch(h *common.BatchHeader) (*types.Header, error) | ||
CreateEthBlockFromBatch(b *core.Batch) (*types.Block, error) | ||
} | ||
|
||
type gethEncodingServiceImpl struct { | ||
// conversion is expensive. Cache the converted headers. The key is the hash of the batch. | ||
gethHeaderCache *cache.Cache[*types.Header] | ||
|
||
storage storage.Storage | ||
logger gethlog.Logger | ||
} | ||
|
||
func NewGethEncodingService(storage storage.Storage, logger gethlog.Logger) EncodingService { | ||
// todo (tudor) figure out the best values | ||
ristrettoCache, err := ristretto.NewCache(&ristretto.Config{ | ||
NumCounters: 1000, // number of keys to track frequency of 100. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: comment says 100 but value says 1000 |
||
MaxCost: 1 << 28, // maximum cost of cache (256MB). | ||
BufferItems: 64, // number of keys per Get buffer. Todo - what is this | ||
}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
ristrettoStore := ristretto_store.NewRistretto(ristrettoCache) | ||
|
||
return &gethEncodingServiceImpl{ | ||
gethHeaderCache: cache.New[*types.Header](ristrettoStore), | ||
storage: storage, | ||
logger: logger, | ||
} | ||
} | ||
|
||
// ExtractEthCallMapString extracts the eth_call gethapi.TransactionArgs from an interface{} | ||
// it ensures that : | ||
// - All types are string | ||
|
@@ -226,41 +269,94 @@ func ExtractEthCall(param interface{}) (*gethapi.TransactionArgs, error) { | |
return callMsg, nil | ||
} | ||
|
||
// CreateEthHeaderForBatch - the EVM requires an Ethereum "block" header. | ||
// In this function we are creating one from the Batch Header | ||
func CreateEthHeaderForBatch(h *common.BatchHeader, secret []byte) (*types.Header, error) { | ||
// deterministically calculate private randomness that will be exposed to the evm | ||
randomness := crypto.CalculateRootBatchEntropy(secret, h.Number) | ||
// CreateEthHeaderForBatch - the EVM requires an Ethereum header. | ||
// We convert the Batch headers to Ethereum headers to be able to use the Geth EVM. | ||
// Special care must be taken to maintain a valid chain of these converted headers. | ||
func (enc *gethEncodingServiceImpl) CreateEthHeaderForBatch(h *common.BatchHeader) (*types.Header, error) { | ||
// wrap in a caching layer | ||
return common.GetCachedValue(enc.gethHeaderCache, enc.logger, h.Hash(), func(a any) (*types.Header, error) { | ||
// deterministically calculate the private randomness that will be exposed to the EVM | ||
secret, err := enc.storage.FetchSecret() | ||
if err != nil { | ||
enc.logger.Crit("Could not fetch shared secret. Exiting.", log.ErrKey, err) | ||
} | ||
perBatchRandomness := crypto.CalculateRootBatchEntropy(secret[:], h.Number) | ||
|
||
baseFee := uint64(0) | ||
if h.BaseFee != nil { | ||
baseFee = h.BaseFee.Uint64() | ||
} | ||
// calculate the converted hash of the parent, for a correct converted chain | ||
// default to the genesis | ||
convertedParentHash := common.GethGenesisParentHash | ||
|
||
if h.SequencerOrderNo.Uint64() > common.L2GenesisSeqNo { | ||
convertedParentHash, err = enc.storage.FetchConvertedHash(h.ParentHash) | ||
if err != nil { | ||
enc.logger.Error("Cannot find the converted value for the parent of", log.BatchSeqNoKey, h.SequencerOrderNo) | ||
return nil, err | ||
} | ||
} | ||
|
||
return &types.Header{ | ||
ParentHash: h.ParentHash, | ||
Root: h.Root, | ||
TxHash: h.TxHash, | ||
ReceiptHash: h.ReceiptHash, | ||
Difficulty: big.NewInt(0), | ||
Number: h.Number, | ||
GasLimit: h.GasLimit, | ||
GasUsed: h.GasUsed, | ||
BaseFee: big.NewInt(0).SetUint64(baseFee), | ||
Coinbase: h.Coinbase, | ||
Time: h.Time, | ||
MixDigest: randomness, | ||
Nonce: types.BlockNonce{}, | ||
}, nil | ||
baseFee := uint64(0) | ||
if h.BaseFee != nil { | ||
baseFee = h.BaseFee.Uint64() | ||
} | ||
|
||
gethHeader := types.Header{ | ||
ParentHash: convertedParentHash, | ||
UncleHash: gethcommon.Hash{}, | ||
Root: h.Root, | ||
TxHash: h.TxHash, | ||
ReceiptHash: h.ReceiptHash, | ||
Difficulty: big.NewInt(0), | ||
Number: h.Number, | ||
GasLimit: h.GasLimit, | ||
GasUsed: h.GasUsed, | ||
BaseFee: big.NewInt(0).SetUint64(baseFee), | ||
Coinbase: h.Coinbase, | ||
Time: h.Time, | ||
MixDigest: perBatchRandomness, | ||
Nonce: types.BlockNonce{}, | ||
Extra: h.SequencerOrderNo.Bytes(), | ||
WithdrawalsHash: nil, | ||
BlobGasUsed: nil, | ||
ExcessBlobGas: nil, | ||
Bloom: types.Bloom{}, | ||
} | ||
return &gethHeader, nil | ||
}) | ||
} | ||
|
||
func CreateEthBlockFromBatch(b *core.Batch) (*types.Block, error) { | ||
blockHeader, err := CreateEthHeaderForBatch(b.Header, nil) | ||
// The Geth "Block" type doesn't expose the header directly. | ||
// This type is required for adjusting the header. | ||
type localBlock struct { | ||
header *types.Header | ||
uncles []*types.Header | ||
transactions types.Transactions | ||
withdrawals types.Withdrawals | ||
|
||
// caches | ||
hash atomic.Value | ||
size atomic.Value | ||
|
||
// These fields are used by package eth to track | ||
// inter-peer block relay. | ||
ReceivedAt time.Time | ||
ReceivedFrom interface{} | ||
} | ||
|
||
func (enc *gethEncodingServiceImpl) CreateEthBlockFromBatch(b *core.Batch) (*types.Block, error) { | ||
blockHeader, err := enc.CreateEthHeaderForBatch(b.Header) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to create eth block from batch - %w", err) | ||
} | ||
|
||
return types.NewBlock(blockHeader, b.Transactions, nil, nil, trie.NewStackTrie(nil)), nil | ||
// adjust the header of the returned block to make sure the hashes align | ||
lb := localBlock{ | ||
header: blockHeader, | ||
uncles: nil, | ||
transactions: b.Transactions, | ||
withdrawals: nil, | ||
} | ||
block := *(*types.Block)(unsafe.Pointer(&lb)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe worth a comment on this line to say something like "these operations force casting of the localBlock to the geth Block type. This was necessary because we can't access the header directly on geth Block" which I think is what's going on? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I literally have this comment on the localBlock type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah sorry, I guess maybe worth a |
||
return &block, nil | ||
} | ||
|
||
// DecodeParamBytes decodes the parameters byte array into a slice of interfaces | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
CacheValue
function stores a value in the cache and logs an error if it fails. The error handling is correct, but it might be beneficial to consider whether the error should be returned to the caller instead of just logged.Consider returning the error from
CacheValue
to allow the caller to handle it as needed.