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  }