github.com/kotalco/kotal@v0.3.0/clients/ethereum/geth_client.go (about)

     1  package ethereum
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  
     8  	ethereumv1alpha1 "github.com/kotalco/kotal/apis/ethereum/v1alpha1"
     9  	sharedAPI "github.com/kotalco/kotal/apis/shared"
    10  	"github.com/kotalco/kotal/controllers/shared"
    11  	corev1 "k8s.io/api/core/v1"
    12  )
    13  
    14  // GethClient is Go-Ethereum client
    15  // https://github.com/ethereum/go-ethereum
    16  type GethClient struct {
    17  	node *ethereumv1alpha1.Node
    18  }
    19  
    20  const (
    21  	// GethHomeDir is go-ethereum docker image home directory
    22  	GethHomeDir = "/home/ethereum"
    23  )
    24  
    25  var (
    26  	verbosityLevels = map[sharedAPI.VerbosityLevel]string{
    27  		sharedAPI.NoLogs:    "0",
    28  		sharedAPI.ErrorLogs: "1",
    29  		sharedAPI.WarnLogs:  "2",
    30  		sharedAPI.InfoLogs:  "3",
    31  		sharedAPI.DebugLogs: "4",
    32  		sharedAPI.AllLogs:   "5",
    33  	}
    34  )
    35  
    36  // HomeDir returns go-ethereum docker image home directory
    37  func (g *GethClient) HomeDir() string {
    38  	return GethHomeDir
    39  }
    40  
    41  func (g *GethClient) Command() []string {
    42  	return nil
    43  }
    44  
    45  func (g *GethClient) Env() []corev1.EnvVar {
    46  	return nil
    47  }
    48  
    49  // Args returns command line arguments required for client run
    50  func (g *GethClient) Args() (args []string) {
    51  
    52  	node := g.node
    53  
    54  	args = append(args, GethDataDir, shared.PathData(g.HomeDir()))
    55  	args = append(args, GethDisableIPC)
    56  	args = append(args, GethP2PPort, fmt.Sprintf("%d", node.Spec.P2PPort))
    57  	args = append(args, GethSyncMode, string(node.Spec.SyncMode))
    58  	if g.node.Spec.SyncMode == ethereumv1alpha1.FullSynchronization {
    59  		args = append(args, GethGcMode, "archive")
    60  		args = append(args, GethHistoryTxs, "0")
    61  		args = append(args, GethCachePreImages)
    62  	}
    63  	args = append(args, GethLogging, verbosityLevels[node.Spec.Logging])
    64  
    65  	// config.toml holding static nodes
    66  	if len(node.Spec.StaticNodes) != 0 {
    67  		args = append(args, GethConfig, fmt.Sprintf("%s/config.toml", shared.PathConfig(g.HomeDir())))
    68  	}
    69  
    70  	if node.Spec.NodePrivateKeySecretName != "" {
    71  		args = append(args, GethNodeKey, fmt.Sprintf("%s/nodekey", shared.PathSecrets(g.HomeDir())))
    72  	}
    73  
    74  	if len(node.Spec.Bootnodes) != 0 {
    75  		bootnodes := []string{}
    76  		for _, bootnode := range node.Spec.Bootnodes {
    77  			bootnodes = append(bootnodes, string(bootnode))
    78  		}
    79  		args = append(args, GethBootnodes, strings.Join(bootnodes, ","))
    80  	}
    81  
    82  	if node.Spec.Genesis == nil {
    83  		args = append(args, fmt.Sprintf("--%s", node.Spec.Network))
    84  	} else {
    85  		args = append(args, GethNoDiscovery)
    86  		args = append(args, GethNetworkID, fmt.Sprintf("%d", node.Spec.Genesis.NetworkID))
    87  	}
    88  
    89  	if node.Spec.Miner {
    90  		args = append(args, GethMinerEnabled)
    91  		args = append(args, GethMinerCoinbase, string(node.Spec.Coinbase))
    92  		args = append(args, GethUnlock, string(node.Spec.Coinbase))
    93  		args = append(args, GethPassword, fmt.Sprintf("%s/account.password", shared.PathSecrets(g.HomeDir())))
    94  	}
    95  
    96  	if node.Spec.RPC {
    97  		args = append(args, GethRPCHTTPEnabled)
    98  		args = append(args, GethRPCHTTPHost, shared.Host(node.Spec.RPC))
    99  		args = append(args, GethRPCHTTPPort, fmt.Sprintf("%d", node.Spec.RPCPort))
   100  		// JSON-RPC API
   101  		apis := []string{}
   102  		for _, api := range node.Spec.RPCAPI {
   103  			apis = append(apis, string(api))
   104  		}
   105  		commaSeperatedAPIs := strings.Join(apis, ",")
   106  		args = append(args, GethRPCHTTPAPI, commaSeperatedAPIs)
   107  	}
   108  
   109  	if node.Spec.Engine {
   110  		args = append(args, GethAuthRPCPort, fmt.Sprintf("%d", node.Spec.EnginePort))
   111  		jwtSecretPath := fmt.Sprintf("%s/jwt.secret", shared.PathSecrets(g.HomeDir()))
   112  		args = append(args, GethAuthRPCJwtSecret, jwtSecretPath)
   113  	}
   114  	args = append(args, GethAuthRPCAddress, shared.Host(node.Spec.Engine))
   115  
   116  	if node.Spec.WS {
   117  		args = append(args, GethRPCWSEnabled)
   118  		args = append(args, GethRPCWSHost, shared.Host(node.Spec.WS))
   119  		args = append(args, GethRPCWSPort, fmt.Sprintf("%d", node.Spec.WSPort))
   120  		// WebSocket API
   121  		apis := []string{}
   122  		for _, api := range node.Spec.WSAPI {
   123  			apis = append(apis, string(api))
   124  		}
   125  		commaSeperatedAPIs := strings.Join(apis, ",")
   126  		args = append(args, GethRPCWSAPI, commaSeperatedAPIs)
   127  	}
   128  
   129  	if node.Spec.GraphQL {
   130  		args = append(args, GethGraphQLHTTPEnabled)
   131  		//NOTE: .GraphQLPort is ignored because rpc port will be used by graphql server
   132  		// .GraphQLPort will be used in the service that point to the pod
   133  	}
   134  
   135  	if len(node.Spec.Hosts) != 0 {
   136  		commaSeperatedHosts := strings.Join(node.Spec.Hosts, ",")
   137  		if node.Spec.RPC {
   138  			args = append(args, GethRPCHostWhitelist, commaSeperatedHosts)
   139  		}
   140  		if node.Spec.GraphQL {
   141  			args = append(args, GethGraphQLHostWhitelist, commaSeperatedHosts)
   142  		}
   143  		if node.Spec.Engine {
   144  			args = append(args, GethAuthRPCHosts, commaSeperatedHosts)
   145  		}
   146  		// no ws hosts settings
   147  	}
   148  
   149  	if len(node.Spec.CORSDomains) != 0 {
   150  		commaSeperatedDomains := strings.Join(node.Spec.CORSDomains, ",")
   151  		if node.Spec.RPC {
   152  			args = append(args, GethRPCHTTPCorsOrigins, commaSeperatedDomains)
   153  		}
   154  		if node.Spec.GraphQL {
   155  			args = append(args, GethGraphQLHTTPCorsOrigins, commaSeperatedDomains)
   156  		}
   157  		if node.Spec.WS {
   158  			args = append(args, GethWSOrigins, commaSeperatedDomains)
   159  		}
   160  	}
   161  
   162  	return args
   163  }
   164  
   165  // EncodeStaticNodes returns the static nodes
   166  // [Node.P2P]
   167  // StaticNodes = [enodeURL1, enodeURL2 ...]
   168  func (g *GethClient) EncodeStaticNodes() string {
   169  
   170  	var encoded []byte
   171  
   172  	if len(g.node.Spec.StaticNodes) == 0 {
   173  		encoded = []byte("[]")
   174  	} else {
   175  		encoded, _ = json.Marshal(g.node.Spec.StaticNodes)
   176  	}
   177  
   178  	return fmt.Sprintf("[Node.P2P]\nStaticNodes = %s", string(encoded))
   179  }
   180  
   181  // Genesis returns genesis config parameter
   182  func (g *GethClient) Genesis() (content string, err error) {
   183  	node := g.node
   184  	genesis := node.Spec.Genesis
   185  	mixHash := genesis.MixHash
   186  	nonce := genesis.Nonce
   187  	extraData := "0x00"
   188  	difficulty := genesis.Difficulty
   189  	result := map[string]interface{}{}
   190  
   191  	var consensusConfig map[string]uint
   192  	var engine string
   193  
   194  	// ethash PoW settings
   195  	if genesis.Ethash != nil {
   196  		consensusConfig = map[string]uint{}
   197  		engine = "ethash"
   198  	}
   199  
   200  	// clique PoA settings
   201  	if genesis.Clique != nil {
   202  		consensusConfig = map[string]uint{
   203  			"period": genesis.Clique.BlockPeriod,
   204  			"epoch":  genesis.Clique.EpochLength,
   205  		}
   206  		engine = "clique"
   207  		extraData = createExtraDataFromSigners(genesis.Clique.Signers)
   208  	}
   209  
   210  	config := map[string]interface{}{
   211  		"chainId":             genesis.ChainID,
   212  		"homesteadBlock":      genesis.Forks.Homestead,
   213  		"eip150Block":         genesis.Forks.EIP150,
   214  		"eip155Block":         genesis.Forks.EIP155,
   215  		"eip158Block":         genesis.Forks.EIP158,
   216  		"byzantiumBlock":      genesis.Forks.Byzantium,
   217  		"constantinopleBlock": genesis.Forks.Constantinople,
   218  		"petersburgBlock":     genesis.Forks.Petersburg,
   219  		"istanbulBlock":       genesis.Forks.Istanbul,
   220  		"muirGlacierBlock":    genesis.Forks.MuirGlacier,
   221  		"berlinBlock":         genesis.Forks.Berlin,
   222  		"londonBlock":         genesis.Forks.London,
   223  		"arrowGlacierBlock":   genesis.Forks.ArrowGlacier,
   224  		engine:                consensusConfig,
   225  	}
   226  
   227  	if genesis.Forks.DAO != nil {
   228  		config["daoForkBlock"] = genesis.Forks.DAO
   229  		config["daoForkSupport"] = true
   230  	}
   231  
   232  	result["config"] = config
   233  
   234  	result["nonce"] = nonce
   235  	result["timestamp"] = genesis.Timestamp
   236  	result["gasLimit"] = genesis.GasLimit
   237  	result["difficulty"] = difficulty
   238  	result["coinbase"] = genesis.Coinbase
   239  	result["mixHash"] = mixHash
   240  	result["extraData"] = extraData
   241  
   242  	alloc := genesisAccounts(false, genesis.Forks)
   243  	for _, account := range genesis.Accounts {
   244  		m := map[string]interface{}{
   245  			"balance": account.Balance,
   246  		}
   247  
   248  		if account.Code != "" {
   249  			m["code"] = account.Code
   250  		}
   251  
   252  		if account.Storage != nil {
   253  			m["storage"] = account.Storage
   254  		}
   255  
   256  		alloc[string(account.Address)] = m
   257  	}
   258  
   259  	result["alloc"] = alloc
   260  
   261  	data, err := json.Marshal(result)
   262  	if err != nil {
   263  		return
   264  	}
   265  
   266  	content = string(data)
   267  
   268  	return
   269  }