github.com/kotalco/kotal@v0.3.0/clients/ethereum/besu_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 "github.com/kotalco/kotal/controllers/shared" 10 corev1 "k8s.io/api/core/v1" 11 ) 12 13 // BesuClient is Hyperledger Besu client 14 // https://github.com/hyperledger/besu 15 type BesuClient struct { 16 node *ethereumv1alpha1.Node 17 } 18 19 const ( 20 // BesuHomeDir is besu docker image home directory 21 BesuHomeDir = "/opt/besu" 22 ) 23 24 // HomeDir returns besu client home directory 25 func (b *BesuClient) HomeDir() string { 26 return BesuHomeDir 27 } 28 29 func (b *BesuClient) Command() []string { 30 return nil 31 } 32 33 func (b *BesuClient) Env() []corev1.EnvVar { 34 return nil 35 } 36 37 // Args returns command line arguments required for client run 38 func (b *BesuClient) Args() (args []string) { 39 40 node := b.node 41 42 args = append(args, BesuNatMethod, "KUBERNETES") 43 args = append(args, BesuDataPath, shared.PathData(b.HomeDir())) 44 args = append(args, BesuP2PPort, fmt.Sprintf("%d", node.Spec.P2PPort)) 45 args = append(args, BesuSyncMode, string(node.Spec.SyncMode)) 46 args = append(args, BesuLogging, strings.ToUpper(string(node.Spec.Logging))) 47 48 if node.Spec.NodePrivateKeySecretName != "" { 49 args = append(args, BesuNodePrivateKey, fmt.Sprintf("%s/nodekey", shared.PathSecrets(b.HomeDir()))) 50 } 51 52 if len(node.Spec.StaticNodes) != 0 { 53 args = append(args, BesuStaticNodesFile, fmt.Sprintf("%s/static-nodes.json", shared.PathConfig(b.HomeDir()))) 54 } 55 56 if len(node.Spec.Bootnodes) != 0 { 57 bootnodes := []string{} 58 for _, bootnode := range node.Spec.Bootnodes { 59 bootnodes = append(bootnodes, string(bootnode)) 60 } 61 args = append(args, BesuBootnodes, strings.Join(bootnodes, ",")) 62 } 63 64 // public network 65 if node.Spec.Genesis == nil { 66 args = append(args, BesuNetwork, node.Spec.Network) 67 } else { // private network 68 args = append(args, BesuGenesisFile, fmt.Sprintf("%s/genesis.json", shared.PathConfig(b.HomeDir()))) 69 args = append(args, BesuNetworkID, fmt.Sprintf("%d", node.Spec.Genesis.NetworkID)) 70 args = append(args, BesuDiscoveryEnabled, "false") 71 } 72 73 if node.Spec.Miner { 74 args = append(args, BesuMinerEnabled) 75 args = append(args, BesuMinerCoinbase, string(node.Spec.Coinbase)) 76 } 77 78 // convert spec rpc modules into format suitable for cli option 79 normalizedAPIs := func(modules []ethereumv1alpha1.API) string { 80 apis := []string{} 81 for _, api := range modules { 82 apis = append(apis, strings.ToUpper(string(api))) 83 } 84 return strings.Join(apis, ",") 85 } 86 87 if node.Spec.RPC { 88 args = append(args, BesuRPCHTTPEnabled) 89 args = append(args, BesuRPCHTTPHost, shared.Host(node.Spec.RPC)) 90 args = append(args, BesuRPCHTTPPort, fmt.Sprintf("%d", node.Spec.RPCPort)) 91 args = append(args, BesuRPCHTTPAPI, normalizedAPIs(node.Spec.RPCAPI)) 92 } 93 94 if node.Spec.Engine { 95 args = append(args, BesuEngineRpcEnabled) 96 args = append(args, BesuEngineRpcPort, fmt.Sprintf("%d", node.Spec.EnginePort)) 97 jwtSecretPath := fmt.Sprintf("%s/jwt.secret", shared.PathSecrets(b.HomeDir())) 98 args = append(args, BesuEngineJwtSecret, jwtSecretPath) 99 } 100 101 if node.Spec.WS { 102 args = append(args, BesuRPCWSEnabled) 103 args = append(args, BesuRPCWSHost, shared.Host(node.Spec.WS)) 104 args = append(args, BesuRPCWSPort, fmt.Sprintf("%d", node.Spec.WSPort)) 105 args = append(args, BesuRPCWSAPI, normalizedAPIs(node.Spec.WSAPI)) 106 } 107 108 if node.Spec.GraphQL { 109 args = append(args, BesuGraphQLHTTPEnabled) 110 args = append(args, BesuGraphQLHTTPHost, shared.Host(node.Spec.GraphQL)) 111 args = append(args, BesuGraphQLHTTPPort, fmt.Sprintf("%d", node.Spec.GraphQLPort)) 112 } 113 114 if len(node.Spec.Hosts) != 0 { 115 commaSeperatedHosts := strings.Join(node.Spec.Hosts, ",") 116 args = append(args, BesuHostAllowlist, commaSeperatedHosts) 117 if node.Spec.Engine { 118 args = append(args, BesuEngineHostAllowList, commaSeperatedHosts) 119 } 120 } 121 122 if len(node.Spec.CORSDomains) != 0 { 123 commaSeperatedDomains := strings.Join(node.Spec.CORSDomains, ",") 124 if node.Spec.RPC { 125 args = append(args, BesuRPCHTTPCorsOrigins, commaSeperatedDomains) 126 } 127 if node.Spec.GraphQL { 128 args = append(args, BesuGraphQLHTTPCorsOrigins, commaSeperatedDomains) 129 } 130 // no ws cors setting 131 } 132 133 return args 134 } 135 136 // Genesis returns genesis config parameter 137 func (b *BesuClient) Genesis() (content string, err error) { 138 node := b.node 139 genesis := node.Spec.Genesis 140 mixHash := genesis.MixHash 141 nonce := genesis.Nonce 142 extraData := "0x00" 143 difficulty := genesis.Difficulty 144 result := map[string]interface{}{} 145 146 var consensusConfig map[string]uint 147 var engine string 148 149 // ethash PoW settings 150 if genesis.Ethash != nil { 151 consensusConfig = map[string]uint{} 152 153 if genesis.Ethash.FixedDifficulty != nil { 154 consensusConfig["fixeddifficulty"] = *genesis.Ethash.FixedDifficulty 155 } 156 157 engine = "ethash" 158 } 159 160 // clique PoA settings 161 if genesis.Clique != nil { 162 consensusConfig = map[string]uint{ 163 "blockperiodseconds": genesis.Clique.BlockPeriod, 164 "epochlength": genesis.Clique.EpochLength, 165 } 166 engine = "clique" 167 extraData = createExtraDataFromSigners(genesis.Clique.Signers) 168 } 169 170 // clique ibft2 settings 171 if genesis.IBFT2 != nil { 172 173 consensusConfig = map[string]uint{ 174 "blockperiodseconds": genesis.IBFT2.BlockPeriod, 175 "epochlength": genesis.IBFT2.EpochLength, 176 "requesttimeoutseconds": genesis.IBFT2.RequestTimeout, 177 "messageQueueLimit": genesis.IBFT2.MessageQueueLimit, 178 "duplicateMessageLimit": genesis.IBFT2.DuplicateMessageLimit, 179 "futureMessagesLimit": genesis.IBFT2.FutureMessagesLimit, 180 "futureMessagesMaxDistance": genesis.IBFT2.FutureMessagesMaxDistance, 181 } 182 engine = "ibft2" 183 mixHash = "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365" 184 nonce = "0x0" 185 difficulty = "0x1" 186 extraData, err = createExtraDataFromValidators(genesis.IBFT2.Validators) 187 if err != nil { 188 return 189 } 190 } 191 192 config := map[string]interface{}{ 193 "chainId": genesis.ChainID, 194 "homesteadBlock": genesis.Forks.Homestead, 195 "eip150Block": genesis.Forks.EIP150, 196 "eip155Block": genesis.Forks.EIP155, 197 "eip158Block": genesis.Forks.EIP158, 198 "byzantiumBlock": genesis.Forks.Byzantium, 199 "constantinopleBlock": genesis.Forks.Constantinople, 200 "petersburgBlock": genesis.Forks.Petersburg, 201 "istanbulBlock": genesis.Forks.Istanbul, 202 "muirGlacierBlock": genesis.Forks.MuirGlacier, 203 "berlinBlock": genesis.Forks.Berlin, 204 "londonBlock": genesis.Forks.London, 205 "arrowGlacierBlock": genesis.Forks.ArrowGlacier, 206 engine: consensusConfig, 207 } 208 209 if genesis.Forks.DAO != nil { 210 config["daoForkBlock"] = genesis.Forks.DAO 211 } 212 213 // If london fork is activated at genesis block 214 // set baseFeePerGas to 0x3B9ACA00 215 // https://discord.com/channels/697535391594446898/743193040197386451/900791897700859916 216 if genesis.Forks.London == 0 { 217 result["baseFeePerGas"] = "0x3B9ACA00" 218 } 219 220 result["config"] = config 221 result["nonce"] = nonce 222 result["timestamp"] = genesis.Timestamp 223 result["gasLimit"] = genesis.GasLimit 224 result["difficulty"] = difficulty 225 result["coinbase"] = genesis.Coinbase 226 result["mixHash"] = mixHash 227 result["extraData"] = extraData 228 229 alloc := genesisAccounts(false, genesis.Forks) 230 for _, account := range genesis.Accounts { 231 m := map[string]interface{}{ 232 "balance": account.Balance, 233 } 234 235 if account.Code != "" { 236 m["code"] = account.Code 237 } 238 239 if account.Storage != nil { 240 m["storage"] = account.Storage 241 } 242 243 alloc[string(account.Address)] = m 244 } 245 246 result["alloc"] = alloc 247 248 data, err := json.Marshal(result) 249 if err != nil { 250 return 251 } 252 253 content = string(data) 254 255 return 256 } 257 258 // EncodeStaticNodes returns the static nodes, one per line 259 func (b *BesuClient) EncodeStaticNodes() string { 260 261 if len(b.node.Spec.StaticNodes) == 0 { 262 return "[]" 263 } 264 265 encoded, _ := json.Marshal(b.node.Spec.StaticNodes) 266 return string(encoded) 267 }