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 }