Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/ten-protocol/go-ten into je…
Browse files Browse the repository at this point in the history
…nnifer/2964-update-tenscan-logo
  • Loading branch information
Jennievon committed Feb 17, 2024
2 parents 9d1c0ab + 597cf0e commit 81dbc63
Show file tree
Hide file tree
Showing 7 changed files with 464 additions and 5 deletions.
23 changes: 21 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,26 @@
---
# Ten Testnet Change Log

# Oct 2024-01-15 (v0.21.0)
# Feb 2024-02-16 (v0.22.0)
* Validator nodes now return errors on transaction submission. Previously, transactions that would fail validation were
accepted into the mempool of the validator without error (e.g. insufficient funds for a transfer, gas below the
intrinsic gas limit etc). This change will mean that invalid transactions are rejected immediately.
* The Gateway now performs caching on RPC requests. For a list of the cached requests see the design proposal in
go-ten/design/gateway/gateway_caching.md. Note that we expect the caching logic to change over time as we optimise
the user experience.
* A full list of the PRs merged in this release is as below;
* `5b5e7d98` Ten gateway caching (#1779)
* `7792864a` Adjust port offset (#1798)
* `1837bcc6` More flakiness fixes (#1795)
* `83469ca8` Validator nodes return error on tx submission (#1792)
* `aee40a81` Network tests: fix local run gas error (#1789)
* `337c8544` Improve multi-acct logic (#1793)
* `a5061634` Copy update (#1791)
* `4d79eea2` Tenscan: update api domain (#1790)
* `c83b965a` Contract deployers: configure signer for address publishing (#1676)
* `31e25322` Refactor the encryption/vk logic (#1769)

# Feb 2024-02-14 (v0.21.0)
* A list of the PRs merged in this release is as below;
* `cc3b1048` Upgrade oz libraries (#1707)
* `0a573d75` Deploy scripts: enable debug api on upgrades (#1780)
Expand Down Expand Up @@ -84,7 +103,7 @@
* `4551c81b` Enclave no panic for bad rlp format (#1679)
* `fb50c7ba` Use the cached head block (#1678)

# Oct 2023-12-01 (v0.20.0)
# Dec 2023-12-01 (v0.20.0)
* A list of the PRs merged in this release is as below;
* `1433e41e` Tenscan updates (#1661)
* `dc769561` Host rpc: use checksum format for contract addresses (#1674)
Expand Down
85 changes: 85 additions & 0 deletions design/gateway/gateway_caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Ten Gateway Caching Design

## 1. Why cache requests on Gateway?

Currently, all `eth_` requests that hit the gateway are forwarded to the Ten Nodes and are executed by the Ten enclave.
This is not ideal since there is only one Sequencer and it can be overloaded with requests
and there can be timeouts or errors in the sequencer.

To solve this problem, we can cache the responses of the `eth_` requests on the Gateway.
Not all requests can be cached, that is why I analyzed the Ethereum JSON-RPC Specification and formed two groups of requests that can be cached and those that cannot be cached.
For some methods, I used a rule of thumb since results can be cached for a certain period of time, but quickly become outdated.


Cacheable request methods are:

- `eth_accounts`
- `eth_chainID`
- `eth_coinbase`
- `eth_getBlockByHash`
- `eth_getBlockByNumber`
- `eth_getBlockRecipients`
- `eth_getBlockTransactionCountByHash`
- `eth_getBlockTransactionCountByNumber`
- `eth_getCode`
- `eth_getStorageAt`
- `eth_getTransactionByBlockHashAndIndex`
- `eth_getTransactionByBlockNumberAndIndex`
- `eth_getTransactionByHash`
- `eth_getTransactionReceipt`
- `eth_getUncleCountByBlockHash`
- `eth_getUncleCountByBlockNumber`
- `eth_maxPriorityFeePerGas`
- `eth_sign`
- `eth_signTransaction`

Methods cachable for a short period of time (TTL until next batch):

- `eth_getBalance`
- `eth_blockNumber`
- `eth_call`
- `eth_createAccessList`
- `eth_estimateGas`
- `eth_feeHistory`
- `eth_getProof`
- `eth_gasPrice`
- `eth_getFilterChanges`
- `eth_getFilterLogs`
- `eth_getTransactionCount`
- `eth_newBlockFilter`
- `eth_newFilter`
- `eth_newPendingTransactionFilter`
- `eth_sendRawTransaction`
- `eth_sendTransaction`
- `eth_syncing`
- `eth_uninstallFilter`

### Expiration time
Some cacheable methods will always produce the same result and can be cached indefinitely, while others will have a short expiration time.
Even for those that can be cached indefinitely I don't think it's a good idea to cache them for a long time,
since a large percentage of the requests will probably be requested only once
and caching will only consume memory and don't provide any benefit.
As far as I can see it can help
to cache the results for shorter amount of time to help reduce the load on the sequencer / help in case of DDoS attacks
(but we need to do also other things to prevent them).



## 3. Implementation

For the implementation we can use some of the Go libraries for caching, such as: https://github.com/patrickmn/go-cache.
All the requests come from a single point in the code, so we can easily add caching to the gateway.



## Pros and cos of caching
I want to present also some negative effects of caching, so we can make a better decision.

### Pros
- it can reduce the load on the sequencer
- cah help if there is a spike of requests (e.g. DDoS attack / high load from specific user)

### Cons
- caching can consume additional memory
- caching can lead to outdated results (only in some methods)
- it is not a good way to prevent DDoS attacks, since users can easily request non-cacheable methods
6 changes: 3 additions & 3 deletions integration/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const (
DefaultEnclaveOffset = 700 // The default offset between a Geth nodes port and the enclave ports. Used in Socket Simulations.
DefaultHostRPCHTTPOffset = 800 // The default offset for the host's RPC HTTP port
DefaultHostRPCWSOffset = 900 // The default offset for the host's RPC websocket port
DefaultTenscanHTTPPortOffset = 1000
DefaultTenGatewayHTTPPortOffset = 1001
DefaultTenGatewayWSPortOffset = 1002
DefaultTenscanHTTPPortOffset = 950
DefaultTenGatewayHTTPPortOffset = 951
DefaultTenGatewayWSPortOffset = 952
)

const (
Expand Down
82 changes: 82 additions & 0 deletions tools/walletextension/cache/RistrettoCache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cache

import (
"time"

"github.com/ethereum/go-ethereum/log"

"github.com/dgraph-io/ristretto"
)

const (
numCounters = 1e7 // number of keys to track frequency of (10M).
maxCost = 1 << 30 // maximum cost of cache (1GB).
bufferItems = 64 // number of keys per Get buffer.
defaultConst = 1 // default cost of cache.
)

type RistrettoCache struct {
cache *ristretto.Cache
quit chan struct{}
}

// NewRistrettoCache returns a new RistrettoCache.
func NewRistrettoCache(logger log.Logger) (*RistrettoCache, error) {
cache, err := ristretto.NewCache(&ristretto.Config{
NumCounters: numCounters,
MaxCost: maxCost,
BufferItems: bufferItems,
Metrics: true,
})
if err != nil {
return nil, err
}

c := &RistrettoCache{
cache: cache,
quit: make(chan struct{}),
}

// Start the metrics logging
go c.startMetricsLogging(logger)

return c, nil
}

// Set adds the key and value to the cache.
func (c *RistrettoCache) Set(key string, value map[string]interface{}, ttl time.Duration) bool {
return c.cache.SetWithTTL(key, value, defaultConst, ttl)
}

// Get returns the value for the given key if it exists.
func (c *RistrettoCache) Get(key string) (value map[string]interface{}, ok bool) {
item, found := c.cache.Get(key)
if !found {
return nil, false
}

// Assuming the item is stored as a map[string]interface{}, otherwise you need to type assert to the correct type.
value, ok = item.(map[string]interface{})
if !ok {
// The item isn't of type map[string]interface{}
return nil, false
}

return value, true
}

// startMetricsLogging starts logging cache metrics every hour.
func (c *RistrettoCache) startMetricsLogging(logger log.Logger) {
ticker := time.NewTicker(1 * time.Hour)
for {
select {
case <-ticker.C:
metrics := c.cache.Metrics
logger.Info("Cache metrics: Hits: %d, Misses: %d, Cost Added: %d\n",
metrics.Hits(), metrics.Misses(), metrics.CostAdded())
case <-c.quit:
ticker.Stop()
return
}
}
}
98 changes: 98 additions & 0 deletions tools/walletextension/cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package cache

import (
"crypto/sha256"
"encoding/json"
"fmt"
"time"

"github.com/ethereum/go-ethereum/log"

"github.com/ten-protocol/go-ten/tools/walletextension/common"
)

const (
longCacheTTL = 5 * time.Hour
shortCacheTTL = 1 * time.Second
)

// CacheableRPCMethods is a map of Ethereum JSON-RPC methods that can be cached and their TTL
var cacheableRPCMethods = map[string]time.Duration{
// Ethereum JSON-RPC methods that can be cached long time
"eth_getBlockByNumber": longCacheTTL,
"eth_getBlockByHash": longCacheTTL,
"eth_getTransactionByHash": longCacheTTL,
"eth_chainId": longCacheTTL,

// Ethereum JSON-RPC methods that can be cached short time
"eth_blockNumber": shortCacheTTL,
"eth_getCode": shortCacheTTL,
"eth_getBalance": shortCacheTTL,
"eth_getTransactionReceipt": shortCacheTTL,
"eth_call": shortCacheTTL,
"eth_gasPrice": shortCacheTTL,
"eth_getTransactionCount": shortCacheTTL,
"eth_estimateGas": shortCacheTTL,
"eth_feeHistory": shortCacheTTL,
}

type Cache interface {
Set(key string, value map[string]interface{}, ttl time.Duration) bool
Get(key string) (value map[string]interface{}, ok bool)
}

func NewCache(logger log.Logger) (Cache, error) {
return NewRistrettoCache(logger)
}

// IsCacheable checks if the given RPC request is cacheable and returns the cache key and TTL
func IsCacheable(key *common.RPCRequest) (bool, string, time.Duration) {
if key == nil || key.Method == "" {
return false, "", 0
}

// Check if the method is cacheable
ttl, isCacheable := cacheableRPCMethods[key.Method]

if isCacheable {
// method is cacheable - select cache key
switch key.Method {
case "eth_getCode", "eth_getBalance", "eth_getTransactionCount", "eth_estimateGas", "eth_call":
if len(key.Params) == 1 || len(key.Params) == 2 && (key.Params[1] == "latest" || key.Params[1] == "pending") {
return true, GenerateCacheKey(key.Method, key.Params...), ttl
}
// in this case, we have a fixed block number, and we can cache the result for a long time
return true, GenerateCacheKey(key.Method, key.Params...), longCacheTTL
case "eth_feeHistory":
if len(key.Params) == 2 || len(key.Params) == 3 && (key.Params[2] == "latest" || key.Params[2] == "pending") {
return true, GenerateCacheKey(key.Method, key.Params...), ttl
}
// in this case, we have a fixed block number, and we can cache the result for a long time
return true, GenerateCacheKey(key.Method, key.Params...), longCacheTTL
default:
return true, GenerateCacheKey(key.Method, key.Params...), ttl
}
}

// method is not cacheable
return false, "", 0
}

// GenerateCacheKey generates a cache key for the given method and parameters
func GenerateCacheKey(method string, params ...interface{}) string {
// Serialize parameters
paramBytes, err := json.Marshal(params)
if err != nil {
return ""
}

// Concatenate method name and parameters
rawKey := method + string(paramBytes)

// Optional: Apply hashing
hasher := sha256.New()
hasher.Write([]byte(rawKey))
hashedKey := fmt.Sprintf("%x", hasher.Sum(nil))

return hashedKey
}
Loading

0 comments on commit 81dbc63

Please sign in to comment.