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

client/assets/eth: in-process geth demo #1010

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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 client/asset/eth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eth
306 changes: 306 additions & 0 deletions client/asset/eth/eth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
package main

import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)

type ethereumI interface {
APIs() []rpc.API
Stop() error
}

type ETH struct {
ctx context.Context
client *ethclient.Client
rpcClient *rpc.Client
shutdown context.CancelFunc
nodeCfg *node.Config
node *node.Node
ethereum ethereumI
}

func NewETH(appDir string, goerli bool) (*ETH, error) {
stackConf := &node.Config{DataDir: appDir}

stackConf.P2P.MaxPeers = 10
var key *ecdsa.PrivateKey
var err error
if key, err = crypto.GenerateKey(); err != nil {
return nil, err
}
stackConf.P2P.PrivateKey = key
Copy link
Member Author

Choose a reason for hiding this comment

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

May or may not have to do this. Needs more testing.

stackConf.P2P.ListenAddr = ":30303"
stackConf.P2P.NAT = nat.Any()

var urls []string
switch {
case goerli:
urls = params.GoerliBootnodes
default:
urls = params.MainnetBootnodes
}

for _, url := range urls {
node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return nil, fmt.Errorf("Bootstrap URL %q invalid: %v", url, err)
}
stackConf.P2P.BootstrapNodes = append(stackConf.P2P.BootstrapNodes, node)
}

for _, url := range params.V5Bootnodes {
node, err := enode.Parse(enode.ValidSchemes, url)
if err != nil {
return nil, fmt.Errorf("Bootstrap v5 URL %q invalid: %v", url, err)
}
stackConf.P2P.BootstrapNodesV5 = append(stackConf.P2P.BootstrapNodesV5, node)
}

stack, err := node.New(stackConf)
if err != nil {
return nil, err
}

ethCfg := ethconfig.Defaults
if goerli {
ethCfg.Genesis = core.DefaultGoerliGenesisBlock()
ethCfg.NetworkId = params.GoerliChainConfig.ChainID.Uint64()
}

// ethCfg.Genesis = core.DefaultGenesisBlock() // eth will do this anyway when Genesis == nil is encountered, and the messaging it better that way.
// ethCfg.SyncMode = downloader.SnapSync // Supposed to be enabled with Berlin on April 14th 2021
// ethCfg.SyncMode = downloader.FastSync // The default
ethCfg.SyncMode = downloader.LightSync
Copy link
Member Author

@buck54321 buck54321 Mar 10, 2021

Choose a reason for hiding this comment

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

Updated here after @JoeGruffins reminded me that fast != light. Much faster now. I'm leaning towards only enabling light mode to start, and then add snap mode option after Berlin.

Copy link
Member

Choose a reason for hiding this comment

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

I got the impression that snap sync was going to be the default after Berlin, but it could actually work now if other nodes are making snapshots. But that's aside because light is the way to go for client.


var ethereum ethereumI
if ethCfg.SyncMode == downloader.LightSync {
ethereum, err = les.New(stack, &ethCfg)
if err != nil {
return nil, err
}
} else {
ethereum, err = eth.New(stack, &ethCfg)
if err != nil {
return nil, err
}
}

if err := stack.Start(); err != nil {
return nil, err
}

// Create a client to interact with local geth node.
rpcClient, err := stack.Attach()
if err != nil {
utils.Fatalf("Failed to attach to self: %v", err)
}
client := ethclient.NewClient(rpcClient)

ctx, shutdown := context.WithCancel(context.Background())
return &ETH{
ctx: ctx,
client: client,
rpcClient: rpcClient,
shutdown: shutdown,
nodeCfg: stackConf,
node: stack,
ethereum: ethereum,
}, nil
}

func (eth *ETH) importAccount(pw string, privKeyB []byte) (accounts.Account, error) {
privKey, err := crypto.ToECDSA(privKeyB)
if err != nil {
return accounts.Account{}, fmt.Errorf("Error restoring private key: %v \n", err)
}

ks := eth.node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
return ks.ImportECDSA(privKey, pw)
}

func (eth *ETH) accountBalance(acct accounts.Account) (*big.Int, error) {
return eth.client.BalanceAt(eth.ctx, acct.Address, nil)
}

//
// type Ethereum struct {
// // unexported fields
// APIBackend *EthAPIBackend
// }
//
// --Methods--
// APIs() []rpc.API
// ResetWithGenesisBlock(gb *types.Block)
// Etherbase() (eb common.Address, err error)
// SetEtherbase(etherbase common.Address)
// StartMining(threads int) error
// StopMining()
// StopMining()
// IsMining() bool
// AccountManager() *accounts.Manager
// BlockChain() *core.BlockChain
// TxPool() *core.TxPool
// EventMux() *event.TypeMux
// Engine() consensus.Engine
// ChainDb() ethdb.Database
// IsListening() bool
// Downloader() *downloader.Downloader
// Synced() bool
// ArchiveMode() bool
// BloomIndexer() *core.ChainIndexer
// Protocols() []p2p.Protocol
// Start() error
// Stop() error

//

// type account.Manager struct {
// no exported fields
// }
//
// --Methods--
// Close() error
// Config() *Config
// Backends(kind reflect.Type) []Backend
// Wallets() []Wallet
// walletsNoLock() []Wallet
// Wallet(url string) (Wallet, error)
// Accounts() []common.Address
// Find(account Account) (Wallet, error)
// Subscribe(sink chan<- WalletEvent) event.Subscription

//

// type EthAPIBackend struct {
// no exported fields
// }
//
// --Methods--
// ChainConfig() *params.ChainConfig
// CurrentBlock() *types.Block
// SetHead(number uint64)
// HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
// HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error)
// HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
// BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
// BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
// BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)
// StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error)
// StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
// GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
// GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error)
// GetTd(ctx context.Context, hash common.Hash) *big.Int
// GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error)
// SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
// SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
// SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
// SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
// SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
// SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
// SendTx(ctx context.Context, signedTx *types.Transaction) error
// GetPoolTransactions() (types.Transactions, error)
// GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
// GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
// Stats() (pending int, queued int)
// TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)
// TxPool() *core.TxPool
// SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription
// Downloader() *downloader.Downloader
// SuggestPrice(ctx context.Context) (*big.Int, error)
// ChainDb() ethdb.Database
// EventMux() *event.TypeMux
// AccountManager() *accounts.Manager
// ExtRPCEnabled() bool
// UnprotectedAllowed() bool
// RPCGasCap() uint64
// RPCTxFeeCap() float64
// BloomStatus() (uint64, uint64)
// ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
// Engine() consensus.Engine
// CurrentHeader() *types.Header
// Miner() *miner.Miner
// StartMining(threads int) error
// StateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, func(), error)
// StatesInRange(ctx context.Context, fromBlock *types.Block, toBlock *types.Block, reexec uint64) ([]*state.StateDB, func(), error)
// StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, func(), error)

//

// type Wallet interface {
// URL() URL
// Status() (string, error)
// Open(passphrase string) error
// Close() error
// Accounts() []Account
// Contains(account Account) bool
// Derive(path DerivationPath, pin bool) (Account, error)
// SelfDerive(bases []DerivationPath, chain ethereum.ChainStateReader)
// SignData(account Account, mimeType string, data []byte) ([]byte, error)
// SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error)
// SignText(account Account, text []byte) ([]byte, error)
// SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
// SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
// SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
// }

//

// type Account struct {
// Address common.Address `json:"address"` // Ethereum account address derived from the key
// URL URL `json:"url"` // Optional resource locator within a backend
// }
//

//

// type Client struct { // ethclient.client
// no exported fields
// }
//
// --Methods--
// Close()
// ChainID(ctx context.Context) (*big.Int, error)
// BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
// BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
// BlockNumber(ctx context.Context) (uint64, error)
// TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error)
// TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error)
// TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
// SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error)
// SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error)
// NetworkID(ctx context.Context) (*big.Int, error)
// BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
// StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error)
// CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
// NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
// FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
// SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
// PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error)
// PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error)
// PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
// PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
// PendingTransactionCount(ctx context.Context) (uint, error)
// CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
// PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error)
// SuggestGasPrice(ctx context.Context) (*big.Int, error)
// EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
// SendTransaction(ctx context.Context, tx *types.Transaction) error
71 changes: 71 additions & 0 deletions client/asset/eth/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package main

import (
"context"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"time"
)

func main() {
tmpDir, _ := ioutil.TempDir("", "")

eth, err := NewETH(tmpDir, true)
if err != nil {
fmt.Println("NewEth error:", err)
os.Exit(1)
}
defer func() {
fmt.Println("Qutting")
eth.shutdown()
eth.node.Close()
eth.ethereum.Stop()
eth.node.Wait()
}()

for _, rpc := range eth.ethereum.APIs() {
fmt.Printf("API namespace = %s: type = %T \n", rpc.Namespace, rpc.Service)
}

pw := "pass"
privB, _ := hex.DecodeString("9447129055a25c8496fca9e5ee1b9463e47e6043ff0c288d07169e8284860e34")
acct, err := eth.importAccount(pw, privB)
if err != nil {
fmt.Print(err)
os.Exit(1)
}

fmt.Println("Account created. Address", acct.Address.Hex())

bal, err := eth.accountBalance(acct)
if err != nil {
fmt.Print(err)
os.Exit(1)
}

fmt.Println("Current account balance:", bal)

tStart := time.Now()
for time.Since(tStart) < time.Minute*360 { // 6 hours
shortCtx, cancel := context.WithTimeout(eth.ctx, time.Second*3)
defer cancel()
block, err := eth.client.BlockByNumber(shortCtx, nil)
if err != nil {
fmt.Println("GetBlock error:", err)
} else {
fmt.Println("Latest block:", block)
}

syncCtx, stop := context.WithTimeout(eth.ctx, time.Second*3)
defer stop()
syncProgress, err := eth.client.SyncProgress(syncCtx)
if err != nil {
fmt.Println("SyncProgress error:", err)
} else {
fmt.Printf("Sync Status: %+v \n", syncProgress)
}
<-time.After(time.Second * 10)
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ require (
github.com/decred/dcrd/wire v1.4.0
github.com/decred/go-socks v1.1.0
github.com/decred/slog v1.1.0
github.com/ethereum/go-ethereum v1.10.1
github.com/gcash/bchd v0.17.2-0.20201218180520-5708823e0e99
github.com/gcash/bchutil v0.0.0-20210113190856-6ea28dff4000
github.com/go-chi/chi v1.5.1
github.com/gorilla/websocket v1.4.2
github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7
github.com/jrick/logrotate v1.0.0
github.com/lib/pq v1.2.0
github.com/smartystreets/goconvey v1.6.4 // indirect
go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
Expand Down
Loading