Skip to content
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

Host: Small cache of blocks by hash #1738

Merged
merged 7 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/mattn/go-sqlite3 v1.14.16
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/pkg/errors v0.9.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
Expand Down
60 changes: 39 additions & 21 deletions go/ethadapter/geth_rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,36 @@
"math/big"
"time"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
gethlog "github.com/ethereum/go-ethereum/log"
"github.com/hashicorp/golang-lru/v2"

Check failure on line 17 in go/ethadapter/geth_rpc_client.go

View workflow job for this annotation

GitHub Actions / lint

File is not `goimports`-ed (goimports)

"github.com/ten-protocol/go-ten/contracts/generated/ManagementContract"
"github.com/ten-protocol/go-ten/go/common/retry"

"github.com/ten-protocol/go-ten/go/common/log"

"github.com/ten-protocol/go-ten/go/common"

"github.com/ethereum/go-ethereum"

gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ten-protocol/go-ten/go/common/log"
"github.com/ten-protocol/go-ten/go/common/retry"
)

const (
connRetryMaxWait = 10 * time.Minute // after this duration, we will stop retrying to connect and return the failure
connRetryInterval = 500 * time.Millisecond
_maxRetryPriceIncreases = 5
_retryPriceMultiplier = 1.2
_defaultBlockCacheSize = 51 // enough for 50 request batch size and one for previous block
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a finger in the air placeholder, I tried it with 200 and it was fine but doesn't give you many more cache hits so I went for a reasonable minimum.

We can make this configurable at some point if it ever becomes useful.

)

// gethRPCClient implements the EthClient interface and allows connection to a real ethereum node
type gethRPCClient struct {
client *ethclient.Client // the underlying eth rpc client
l2ID gethcommon.Address // the address of the Obscuro node this client is dedicated to
timeout time.Duration // the timeout for connecting to, or communicating with, the L1 node
logger gethlog.Logger
rpcURL string
client *ethclient.Client // the underlying eth rpc client
l2ID gethcommon.Address // the address of the Obscuro node this client is dedicated to
timeout time.Duration // the timeout for connecting to, or communicating with, the L1 node
logger gethlog.Logger
rpcURL string
blockCache *lru.Cache[gethcommon.Hash, *types.Block]
}

// NewEthClientFromURL instantiates a new ethadapter.EthClient that connects to an ethereum node
Expand All @@ -49,12 +48,20 @@
}

logger.Trace(fmt.Sprintf("Initialized eth node connection - addr: %s", rpcURL))

// cache recent blocks to avoid re-fetching them (they are often re-used for checking for forks etc.)
blkCache, err := lru.New[gethcommon.Hash, *types.Block](_defaultBlockCacheSize)
if err != nil {
return nil, fmt.Errorf("unable to initialize block cache - %w", err)
}

return &gethRPCClient{
client: client,
l2ID: l2ID,
timeout: timeout,
logger: logger,
rpcURL: rpcURL,
client: client,
l2ID: l2ID,
timeout: timeout,
logger: logger,
rpcURL: rpcURL,
blockCache: blkCache,
}, nil
}

Expand Down Expand Up @@ -181,10 +188,21 @@
}

func (e *gethRPCClient) BlockByHash(hash gethcommon.Hash) (*types.Block, error) {
block, found := e.blockCache.Get(hash)
if found {
return block, nil
}

// not in cache, fetch from RPC
ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
defer cancel()

return e.client.BlockByHash(ctx, hash)
block, err := e.client.BlockByHash(ctx, hash)
if err != nil {
return nil, err
}
e.blockCache.Add(hash, block)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block is added to the cache without checking if the block is nil. Adding a nil block to the cache could lead to unexpected behavior when the cache is accessed later. It would be prudent to add a nil check before caching the block.

+ if block != nil {
+     e.blockCache.Add(hash, block)
+ }

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
e.blockCache.Add(hash, block)
if block != nil {
e.blockCache.Add(hash, block)
}

return block, nil
Comment on lines +192 to +206
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BlockByHash method has been correctly modified to check the cache before making an RPC call. However, there is a potential issue with the type assertion after retrieving the block from the cache. The found value only indicates whether the key exists, not whether the type assertion will succeed. A type assertion should be performed to ensure the value is of the expected type.

- block, found := e.blockCache.Get(hash)
+ cachedBlock, found := e.blockCache.Get(hash)
+ if found {
+     block, ok := cachedBlock.(*types.Block)
+     if !ok {
+         e.logger.Error("cache returned a non-block type")
+         return nil, fmt.Errorf("cache integrity error: expected *types.Block, got %T", cachedBlock)
+     }
+     return block, nil
+ }

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
block, found := e.blockCache.Get(hash)
if found {
return block, nil
}
// not in cache, fetch from RPC
ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
defer cancel()
return e.client.BlockByHash(ctx, hash)
block, err := e.client.BlockByHash(ctx, hash)
if err != nil {
return nil, err
}
e.blockCache.Add(hash, block)
return block, nil
cachedBlock, found := e.blockCache.Get(hash)
if found {
block, ok := cachedBlock.(*types.Block)
if !ok {
e.logger.Error("cache returned a non-block type")
return nil, fmt.Errorf("cache integrity error: expected *types.Block, got %T", cachedBlock)
}
return block, nil
}
// not in cache, fetch from RPC
ctx, cancel := context.WithTimeout(context.Background(), e.timeout)
defer cancel()
block, err := e.client.BlockByHash(ctx, hash)
if err != nil {
return nil, err
}
e.blockCache.Add(hash, block)
return block, nil

}

func (e *gethRPCClient) CallContract(msg ethereum.CallMsg) ([]byte, error) {
Expand Down
Loading