-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
3,587 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
File renamed without changes.
File renamed without changes.
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,60 @@ | ||
package config | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
|
||
"github.com/spf13/viper" | ||
) | ||
|
||
const ( | ||
TargetHost = "targetHost" | ||
TargetPort = "targetPort" | ||
ProxyPort = "proxyPort" | ||
Path = "path" | ||
Network = "network" | ||
) | ||
|
||
var defaults = map[string]interface{}{ | ||
TargetHost: "8525", | ||
TargetPort: "127.0.0.1", | ||
ProxyPort: "9545", | ||
Path: ".", | ||
Network: "mainnet", | ||
} | ||
|
||
var configName string | ||
|
||
func init() { | ||
flag.StringVar(&configName, "config", "config", "Configuration file name (without the extension)") | ||
} | ||
|
||
func Init() { | ||
flag.Parse() | ||
|
||
viper.SetConfigName(configName) | ||
viper.AddConfigPath("/etc/tenderly/") | ||
viper.AddConfigPath("$HOME/.tenderly") | ||
viper.AddConfigPath(".") | ||
|
||
for k, v := range defaults { | ||
viper.SetDefault(k, v) | ||
} | ||
|
||
err := viper.ReadInConfig() | ||
if err != nil { | ||
panic(fmt.Errorf("Fatal error config file: %s \n", err)) | ||
} | ||
} | ||
|
||
func GetString(key string) string { | ||
check(key) | ||
|
||
return viper.GetString(key) | ||
} | ||
|
||
func check(key string) { | ||
if !viper.IsSet(key) { | ||
panic(fmt.Errorf("missing config for key %s", key)) | ||
} | ||
} |
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,3 @@ | ||
# Ethereum Client | ||
|
||
Ethereum client is supposed to be a general interface over any ethereum node. |
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,207 @@ | ||
package client | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"github.com/tenderly/tenderly-cli/ethereum" | ||
"github.com/tenderly/tenderly-cli/ethereum/geth" | ||
"github.com/tenderly/tenderly-cli/ethereum/parity" | ||
|
||
"github.com/tenderly/tenderly-cli/jsonrpc2" | ||
) | ||
|
||
// Client represents an implementation agnostic interface to the Ethereum node. | ||
// It is able connect to both different protocols (http, ws) and implementations (geth, parity). | ||
type Client struct { | ||
rpc *jsonrpc2.Client | ||
schema ethereum.Schema | ||
|
||
openChannels []chan int64 | ||
} | ||
|
||
func Dial(host string) (*Client, error) { | ||
rpcClient, err := jsonrpc2.DiscoverAndDial(host) | ||
if err != nil { | ||
return nil, fmt.Errorf("dial ethereum rpc: %s", err) | ||
} | ||
|
||
nodeType := "geth" | ||
|
||
req, resp := parity.DefaultSchema.Parity().VersionInfo() | ||
if err = rpcClient.CallRequest(resp, req); err == nil { | ||
nodeType = "parity" | ||
} | ||
|
||
var schema ethereum.Schema | ||
switch nodeType { | ||
case "geth": | ||
schema = &geth.DefaultSchema | ||
case "parity": | ||
schema = &parity.DefaultSchema | ||
default: | ||
return nil, fmt.Errorf("unsupported node type: %s", err) | ||
} | ||
|
||
return &Client{ | ||
rpc: rpcClient, | ||
schema: schema, | ||
}, nil | ||
} | ||
|
||
func (c *Client) CurrentBlockNumber() (int64, error) { | ||
req, resp := c.schema.Eth().BlockNumber() | ||
|
||
err := c.rpc.CallRequest(resp, req) | ||
if err != nil { | ||
return 0, fmt.Errorf("current block number: %s", err) | ||
} | ||
|
||
return resp.Value(), nil | ||
} | ||
|
||
func (c *Client) GetBlock(number int64) (ethereum.Block, error) { | ||
req, resp := c.schema.Eth().GetBlockByNumber(ethereum.Number(number)) | ||
|
||
if err := c.rpc.CallRequest(resp, req); err != nil { | ||
return nil, fmt.Errorf("get block by number [%d]: %s", number, err) | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
func (c *Client) GetTransaction(hash string) (ethereum.Transaction, error) { | ||
req, resp := c.schema.Eth().GetTransaction(hash) | ||
|
||
if err := c.rpc.CallRequest(resp, req); err != nil { | ||
return nil, fmt.Errorf("get transaction [%s]: %s", hash, err) | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
func (c *Client) GetTransactionReceipt(hash string) (ethereum.TransactionReceipt, error) { | ||
req, resp := c.schema.Eth().GetTransactionReceipt(hash) | ||
|
||
if err := c.rpc.CallRequest(resp, req); err != nil { | ||
return nil, fmt.Errorf("get transaction receipt [%s]: %s", hash, err) | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
func (c *Client) GetNetworkID() (string, error) { | ||
req, resp := c.schema.Net().Version() | ||
|
||
if err := c.rpc.CallRequest(resp, req); err != nil { | ||
return "", fmt.Errorf("get network ID: %s", err) | ||
} | ||
|
||
return *resp, nil | ||
} | ||
|
||
func (c *Client) GetTransactionVMTrace(hash string) (ethereum.TransactionStates, error) { | ||
req, resp := c.schema.Trace().VMTrace(hash) | ||
|
||
if err := c.rpc.CallRequest(resp, req); err != nil { | ||
return nil, fmt.Errorf("get transaction trace [%s]: %s", hash, err) | ||
} | ||
|
||
resp.ProcessTrace() | ||
|
||
return resp, nil | ||
} | ||
|
||
func (c *Client) GetTransactionCallTrace(hash string) (ethereum.CallTraces, error) { | ||
req, resp := c.schema.Trace().CallTrace(hash) | ||
|
||
if err := c.rpc.CallRequest(resp, req); err != nil { | ||
return nil, fmt.Errorf("get transaction pretty trace [%s]: %s", hash, err) | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
func (c *Client) Subscribe(forcePoll bool) (chan int64, error) { | ||
if forcePoll { | ||
log.Printf("Forcing polling subscription...") | ||
return c.subscribeViaPoll() | ||
} | ||
|
||
//@TODO: Manage closing of the subscription. | ||
req, subscription := c.schema.PubSub().Subscribe() | ||
err := c.rpc.CallRequest(subscription, req) | ||
if err != nil { | ||
//@TODO: Do specific check if subscription not supported. | ||
log.Printf("Subscription not supported, falling back to polling") | ||
return c.subscribeViaPoll() | ||
} | ||
|
||
return c.subscribe(subscription) | ||
} | ||
|
||
func (c *Client) subscribeViaPoll() (chan int64, error) { | ||
outCh := make(chan int64) | ||
|
||
go func() { | ||
var lastBlock int64 | ||
|
||
for { | ||
blockNumber, err := c.CurrentBlockNumber() | ||
if err != nil { | ||
log.Printf("failed pollig for last block number: %s", err) | ||
time.Sleep(1 * time.Second) | ||
continue | ||
} | ||
|
||
if lastBlock == 0 { | ||
lastBlock = blockNumber | ||
continue | ||
} | ||
|
||
for lastBlock < blockNumber { | ||
outCh <- blockNumber | ||
|
||
lastBlock++ | ||
} | ||
|
||
time.Sleep(200 * time.Millisecond) | ||
} | ||
}() | ||
|
||
return outCh, nil | ||
} | ||
|
||
func (c *Client) subscribe(id *ethereum.SubscriptionID) (chan int64, error) { | ||
outCh := make(chan int64) | ||
|
||
inCh, err := c.rpc.Subscribe(id.String()) | ||
if err != nil { | ||
return nil, fmt.Errorf("listen for subscriptions: %s", err) | ||
} | ||
|
||
go func() { | ||
for msg := range inCh { | ||
var resp geth.SubscriptionResult | ||
err = json.Unmarshal(msg.Params, &resp) | ||
if err != nil { | ||
log.Printf("failed reading notification: %s", err) | ||
continue | ||
} | ||
|
||
outCh <- resp.Result.Number().Value() | ||
} | ||
|
||
close(outCh) | ||
}() | ||
|
||
return outCh, nil | ||
} | ||
|
||
func (c *Client) Close() error { | ||
c.rpc.Close() | ||
|
||
return nil | ||
} |
Oops, something went wrong.