Merge pull request #82 from getamis/feature/load-test-tx
Preparation for load testing implementation
This commit is contained in:
commit
74a3b06e4d
|
@ -3,4 +3,5 @@
|
|||
from subprocess import call
|
||||
|
||||
for v in range(0, 26, 1):
|
||||
call(["helm" ,"delete", "--purge", "validator-svc-{}".format(v)])
|
||||
call(["helm" ,"delete", "--purge", "validator-{}".format(v)])
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
from subprocess import call
|
||||
|
||||
for v in range(0, 26, 1):
|
||||
call(["helm" ,"install", "-n", "validator-svc-{}".format(v), "-f", "values.validator-{}.yaml".format(v), "validator-service"])
|
||||
call(["helm" ,"install", "-n", "validator-{}".format(v), "-f", "values.validator-{}.yaml".format(v), "validator"])
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
|
@ -0,0 +1,10 @@
|
|||
name: validator-service
|
||||
home: https://github.com/maichain/mchain_service
|
||||
apiVersion: v1
|
||||
description: A reference Helm chart for Ethereum validator
|
||||
maintainers:
|
||||
- name: Alan Chen
|
||||
email: alan@am.is
|
||||
version: 0.0.1
|
||||
sources:
|
||||
- https://github.com/maichain/mchain_service/kubernetes/amis/generic/validator
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 AMIS Technologies
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,74 @@
|
|||
# Validator Helm Chart
|
||||
|
||||
A validator is an Ethereum client to validate transactions and generate blocks.
|
||||
|
||||
## Prerequisites
|
||||
* [StatefulSets](https://kubernetes.io/docs/concepts/abstractions/controllers/statefulsets/) support
|
||||
* Dynamically provisioned persistent volumes
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
To install the chart:
|
||||
|
||||
```bash
|
||||
helm install \
|
||||
--name ${DEPLOYMENT_ENVIRONMENT}-transactor-${NAME} \
|
||||
kubernetes/amis/generic/validator
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The following tables lists the configurable parameters of the transactor chart and their default values.
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ------------------------------- | ------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| `service.type` | The Kubernetes service type | `ClusterIP` |
|
||||
| `service.externalPeerPort` | Exposed Ethereum P2P port | `30303` |
|
||||
| `service.ExternalRPCPort` | Exposed Ethereum RPC port | `8545` |
|
||||
| `service.ExternalWSPort` | Exposed Ethereum WebSocket port | `8546` |
|
||||
| `replicaCount` | Kubernetes statefulset replicas | `1` |
|
||||
| `image.repository` | Docker image repository | `ethereum/client-go` |
|
||||
| `image.tag` | Docker image tag | `alpine` |
|
||||
| `image.pullPolicy` | Docker image pulling policy | `Always` |
|
||||
| `ethereum.identity` | Custom node name | `$POD_NAME` |
|
||||
| `ethereum.port` | Network listening port | `30303` |
|
||||
| `ethereum.networkID` | Network identifier | `12345` |
|
||||
| `ethereum.cache` | Megabytes of memory allocated to internal caching | `512` |
|
||||
| `ethereum.rpc.enabled` | Enable the HTTP-RPC server | `false` |
|
||||
| `ethereum.rpc.addr` | HTTP-RPC server listening interface | `localhost` |
|
||||
| `ethereum.rpc.port` | HTTP-RPC server listening port | `8545` |
|
||||
| `ethereum.rpc.api` | API's offered over the HTTP-RPC interface | `"eth,net,web3,personal"` |
|
||||
| `ethereum.rpc.corsdomain` | Comma separated list of domains from which to accept cross origin requests | `*` |
|
||||
| `ethereum.ws.enabled` | Enable the WS-RPC server | `false` |
|
||||
| `ethereum.ws.addr` | WS-RPC server listening interface | `localhost` |
|
||||
| `ethereum.ws.port` | WS-RPC server listening port | `8546` |
|
||||
| `ethereum.ws.api` | API's offered over the WS-RPC interface | `"eth,net,web3,personal"` |
|
||||
| `ethereum.ws.origins` | Origins from which to accept websockets requests | `*` |
|
||||
| `ethereum.mining.enabled` | Enable mining | `true` |
|
||||
| `ethereum.mining.threads` | Number of CPU threads to use for mining | `2` |
|
||||
| `ethereum.mining.etherbase` | Public address for block mining rewards | `"1a9afb711302c5f83b5902843d1c007a1a137632"` |
|
||||
| `ethereum.ethstats.enabled` | Enable ethstats reporting | `true` |
|
||||
| `ethereum.ethstats.addr` | Ethstats service address | `ws://eth-netstats` |
|
||||
| `ethereum.ethstats.port` | Ethstats service port | `3000` |
|
||||
| `ethereum.ethstats.secret` | Ethstats service websocket secret | `bb98a0b6442386d0cdf8a31b267892c1` |
|
||||
| `ethereum.nodekey.hex` | P2P node key as hex | `aaaaaaaaaaaaaa5302ccdd5b6ffa092e148ba497e352c2824f366ec399072068` |
|
||||
| `ethereum.account.address` | Account address assigned to the validator | `1a9afb711302c5f83b5902843d1c007a1a137632` |
|
||||
| `ethereum.account.data` | Full account data file as JSON string | `{"address":"1a9afb711302c5f83b5902843d1c007a1a137632","Crypto":{"cipher":"aes-128-ctr","ciphertext":"132b50d7c8944a115824de7c00911c40a90f84f27c614b7a3ef05ee8fd414312","cipherparams":{"iv":"0f745599d1b3303988ce210fb82b8c7f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"bce940bac232b4a9c5a2d50e5be51fde5cecfa7da9d49d8f650f91167bebf0de"},"mac":"36d515070b797aec58a574a3e04ea109498ee7674b15d7f952322cda7dcb68e3"},"id":"5d212b4c-3dd0-4c52-a32f-e42bf1b41133","version":3}` |
|
||||
| `volumes.chaindir.size` | The maximum size usage of Ethereum data directory | `10Gi` |
|
||||
| `volumes.chaindir.storageClass` | The Kubernetes storage class of Ethereum data | `$NAMESPACE-chaindata` |
|
||||
| `global.computingSpec` | The computing spec level of node to schedule | `low` |
|
||||
| `global.nodeType` | The type of node to schedule | `generic` |
|
||||
|
||||
|
||||
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`.
|
||||
|
||||
Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example,
|
||||
|
||||
```bash
|
||||
helm install \
|
||||
--name ${DEPLOYMENT_ENVIRONMENT}-validator-${NAME} \
|
||||
-f values.yaml \
|
||||
kubernetes/amis/generic/validator
|
||||
```
|
||||
|
||||
> **Tip**: You can use the default [values.yaml](values.yaml)
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "name" -}}
|
||||
{{- default .Chart.Name | trunc 60 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 60 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "fullname" -}}
|
||||
{{- $name := .Values.nameOverride -}}
|
||||
{{- printf "%s-%v" .Chart.Name $name | trunc 60 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Deployment environment
|
||||
*/}}
|
||||
{{- define "environment" -}}
|
||||
{{- default .Release.Namespace -}}
|
||||
{{- end -}}
|
|
@ -13,10 +13,6 @@ spec:
|
|||
clusterIP: {{ .Values.service.staticIP | quote }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- port: {{ .Values.service.externalPeerPort }}
|
||||
targetPort: {{ .Values.ethereum.port }}
|
||||
protocol: TCP
|
||||
name: peer
|
||||
{{- if .Values.ethereum.rpc.enabled }}
|
||||
- port: {{ .Values.service.externalRPCPort }}
|
||||
targetPort: {{ .Values.ethereum.rpc.port }}
|
||||
|
@ -30,4 +26,4 @@ spec:
|
|||
name: ws
|
||||
{{- end }}
|
||||
selector:
|
||||
app: {{ template "fullname" . }}
|
||||
app: {{ .Values.app }}
|
|
@ -0,0 +1,21 @@
|
|||
# Default values for validator-service.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
nameOverride:
|
||||
|
||||
# service.yaml
|
||||
service:
|
||||
type: ClusterIP
|
||||
externalRPCPort: 8545
|
||||
externalWSPort: 8546
|
||||
|
||||
ethereum:
|
||||
rpc:
|
||||
enabled: false
|
||||
port: 8545
|
||||
ws:
|
||||
enabled: true
|
||||
port: 8546
|
||||
|
||||
app: validator
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2017 AMIS Technologies
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type ValidatorServiceChart struct {
|
||||
name string
|
||||
chartPath string
|
||||
args []string
|
||||
}
|
||||
|
||||
func NewValidatorServiceChart(name string, args []string) *ValidatorServiceChart {
|
||||
chartPath := filepath.Join(chartBasePath, "validator-service")
|
||||
|
||||
chart := &ValidatorServiceChart{
|
||||
name: "validator-svc-" + name,
|
||||
args: args,
|
||||
chartPath: chartPath,
|
||||
}
|
||||
|
||||
chart.Override("nameOverride", name)
|
||||
chart.Override("service.type", "LoadBalancer")
|
||||
chart.Override("app", "validator-"+name)
|
||||
|
||||
return chart
|
||||
}
|
||||
|
||||
func (chart *ValidatorServiceChart) Override(key, value string) {
|
||||
chart.args = append(chart.args, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
|
||||
func (chart *ValidatorServiceChart) Install(debug bool) error {
|
||||
return installRelease(
|
||||
chart.name,
|
||||
chart.args,
|
||||
chart.chartPath,
|
||||
debug,
|
||||
)
|
||||
}
|
||||
|
||||
func (chart *ValidatorServiceChart) Uninstall() error {
|
||||
return uninstallRelease(chart.name)
|
||||
}
|
||||
|
||||
func (chart *ValidatorServiceChart) Name() string {
|
||||
return chart.name
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2017 AMIS Technologies
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
"github.com/getamis/istanbul-tools/client"
|
||||
)
|
||||
|
||||
func SendEther(client *client.Client, from *ecdsa.PrivateKey, to common.Address, amount *big.Int, nonce uint64) error {
|
||||
tx := types.NewTransaction(nonce, to, amount, nil, nil, []byte{})
|
||||
signedTx, err := types.SignTx(tx, types.EIP155Signer{}, from)
|
||||
if err != nil {
|
||||
log.Error("Failed to sign transaction", "tx", tx, "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = client.SendRawTransaction(context.Background(), tx)
|
||||
if err != nil {
|
||||
log.Error("Failed to send transaction", "tx", signedTx, "nonce", nonce, "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -44,6 +44,10 @@ const (
|
|||
defaultPassword = ""
|
||||
)
|
||||
|
||||
type NodeIncubator interface {
|
||||
CreateNodes(int, ...Option) ([]Ethereum, error)
|
||||
}
|
||||
|
||||
type Blockchain interface {
|
||||
AddValidators(numOfValidators int) ([]Ethereum, error)
|
||||
RemoveValidators(candidates []Ethereum, t time.Duration) error
|
||||
|
@ -52,7 +56,6 @@ type Blockchain interface {
|
|||
Stop(bool) error
|
||||
Validators() []Ethereum
|
||||
Finalize()
|
||||
CreateNodes(int, ...Option) ([]Ethereum, error)
|
||||
}
|
||||
|
||||
func NewBlockchain(network *DockerNetwork, numOfValidators int, options ...Option) (bc *blockchain) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
@ -53,8 +54,8 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrNoBlock = errors.New("no block generated")
|
||||
ErrConsensusTimeout = errors.New("consensus timeout")
|
||||
ErrNoBlock = errors.New("no block generated")
|
||||
ErrTimeout = errors.New("timeout")
|
||||
)
|
||||
|
||||
type Ethereum interface {
|
||||
|
@ -77,6 +78,9 @@ type Ethereum interface {
|
|||
// Want for block for no more than the given number during the given time duration
|
||||
WaitForNoBlocks(int, time.Duration) error
|
||||
|
||||
// Wait for settling balances for the given accounts
|
||||
WaitForBalances([]common.Address, ...time.Duration) error
|
||||
|
||||
AddPeer(string) error
|
||||
|
||||
StartMining() error
|
||||
|
@ -400,6 +404,7 @@ func (eth *ethereum) ConsensusMonitor(errCh chan<- error, quit chan struct{}) {
|
|||
defer sub.Unsubscribe()
|
||||
|
||||
timer := time.NewTimer(10 * time.Second)
|
||||
defer timer.Stop()
|
||||
blockNumber := uint64(0)
|
||||
for {
|
||||
select {
|
||||
|
@ -411,7 +416,7 @@ func (eth *ethereum) ConsensusMonitor(errCh chan<- error, quit chan struct{}) {
|
|||
if blockNumber == 0 {
|
||||
errCh <- ErrNoBlock
|
||||
} else {
|
||||
errCh <- ErrConsensusTimeout
|
||||
errCh <- ErrTimeout
|
||||
}
|
||||
return
|
||||
case head := <-subCh:
|
||||
|
@ -442,6 +447,7 @@ func (eth *ethereum) WaitForProposed(expectedAddress common.Address, timeout tim
|
|||
defer sub.Unsubscribe()
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case err := <-sub.Err():
|
||||
|
@ -464,6 +470,7 @@ func (eth *ethereum) WaitForPeersConnected(expectedPeercount int) error {
|
|||
defer client.Close()
|
||||
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
defer ticker.Stop()
|
||||
for _ = range ticker.C {
|
||||
infos, err := client.AdminPeers(context.Background())
|
||||
if err != nil {
|
||||
|
@ -472,7 +479,6 @@ func (eth *ethereum) WaitForPeersConnected(expectedPeercount int) error {
|
|||
if len(infos) < expectedPeercount {
|
||||
continue
|
||||
} else {
|
||||
ticker.Stop()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -498,6 +504,7 @@ func (eth *ethereum) WaitForBlocks(num int, waitingTime ...time.Duration) error
|
|||
|
||||
timeout := time.After(t)
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
|
@ -514,7 +521,6 @@ func (eth *ethereum) WaitForBlocks(num int, waitingTime ...time.Duration) error
|
|||
}
|
||||
// Check if new blocks are getting generated
|
||||
if new(big.Int).Sub(n, first).Int64() >= int64(num) {
|
||||
ticker.Stop()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -529,13 +535,13 @@ func (eth *ethereum) WaitForBlockHeight(num int) error {
|
|||
defer client.Close()
|
||||
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for _ = range ticker.C {
|
||||
n, err := client.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n.Int64() >= int64(num) {
|
||||
ticker.Stop()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -552,12 +558,13 @@ func (eth *ethereum) WaitForNoBlocks(num int, duration time.Duration) error {
|
|||
}
|
||||
|
||||
timeout := time.After(duration)
|
||||
tick := time.Tick(time.Millisecond * 500)
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
return nil
|
||||
case <-tick:
|
||||
case <-ticker.C:
|
||||
n, err := client.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -574,6 +581,67 @@ func (eth *ethereum) WaitForNoBlocks(num int, duration time.Duration) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (eth *ethereum) WaitForBalances(addrs []common.Address, duration ...time.Duration) error {
|
||||
client := eth.NewClient()
|
||||
if client == nil {
|
||||
return errors.New("failed to retrieve client")
|
||||
}
|
||||
|
||||
var t time.Duration
|
||||
if len(duration) > 0 {
|
||||
t = duration[0]
|
||||
} else {
|
||||
t = 1 * time.Hour
|
||||
}
|
||||
|
||||
waitBalance := func(addr common.Address) error {
|
||||
timeout := time.After(t)
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
return ErrTimeout
|
||||
case <-ticker.C:
|
||||
n, err := client.BalanceAt(context.Background(), addr, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if new blocks are getting generated
|
||||
if n.Uint64() <= 0 {
|
||||
continue
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errc := make(chan error, len(addrs))
|
||||
wg.Add(len(addrs))
|
||||
|
||||
for _, addr := range addrs {
|
||||
addr := addr
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errc <- waitBalance(addr)
|
||||
}()
|
||||
}
|
||||
// Wait for the first error, then terminate the others.
|
||||
var err error
|
||||
for i := 0; i < len(addrs); i++ {
|
||||
if err = <-errc; err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (eth *ethereum) AddPeer(address string) error {
|
||||
client := eth.NewClient()
|
||||
if client == nil {
|
||||
|
|
|
@ -106,10 +106,6 @@ func (bc *blockchain) Validators() []container.Ethereum {
|
|||
return bc.validators
|
||||
}
|
||||
|
||||
func (bc *blockchain) CreateNodes(num int, options ...Option) (nodes []container.Ethereum, err error) {
|
||||
return nil, errors.New("unsupported")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (bc *blockchain) setupValidators(num int, nodekeys []string, ips []string, options ...Option) {
|
||||
|
|
212
k8s/ethereum.go
212
k8s/ethereum.go
|
@ -17,6 +17,11 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -24,10 +29,13 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
"github.com/getamis/go-ethereum/crypto"
|
||||
"github.com/getamis/istanbul-tools/charts"
|
||||
"github.com/getamis/istanbul-tools/client"
|
||||
istcommon "github.com/getamis/istanbul-tools/common"
|
||||
"github.com/getamis/istanbul-tools/container"
|
||||
)
|
||||
|
||||
func NewEthereum(options ...Option) *ethereum {
|
||||
|
@ -39,6 +47,12 @@ func NewEthereum(options ...Option) *ethereum {
|
|||
opt(eth)
|
||||
}
|
||||
|
||||
var err error
|
||||
eth.key, err = crypto.HexToECDSA(eth.nodekey)
|
||||
if err != nil {
|
||||
log.Error("Failed to create private key from nodekey", "nodekey", eth.nodekey)
|
||||
return nil
|
||||
}
|
||||
eth.chart = charts.NewValidatorChart(eth.name, eth.args)
|
||||
|
||||
return eth
|
||||
|
@ -49,6 +63,8 @@ type ethereum struct {
|
|||
name string
|
||||
args []string
|
||||
|
||||
nodekey string
|
||||
key *ecdsa.PrivateKey
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
|
@ -90,7 +106,7 @@ func (eth *ethereum) DockerBinds() []string {
|
|||
}
|
||||
|
||||
func (eth *ethereum) NewClient() *client.Client {
|
||||
client, err := client.Dial("ws://" + eth.Host() + ":8545")
|
||||
client, err := client.Dial("ws://" + eth.Host() + ":8546")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -102,7 +118,7 @@ func (eth *ethereum) NodeAddress() string {
|
|||
}
|
||||
|
||||
func (eth *ethereum) Address() common.Address {
|
||||
return common.Address{}
|
||||
return crypto.PubkeyToAddress(eth.key.PublicKey)
|
||||
}
|
||||
|
||||
func (eth *ethereum) ConsensusMonitor(errCh chan<- error, quit chan struct{}) {
|
||||
|
@ -110,25 +126,211 @@ func (eth *ethereum) ConsensusMonitor(errCh chan<- error, quit chan struct{}) {
|
|||
}
|
||||
|
||||
func (eth *ethereum) WaitForProposed(expectedAddress common.Address, timeout time.Duration) error {
|
||||
return nil
|
||||
cli := eth.NewClient()
|
||||
|
||||
subCh := make(chan *ethtypes.Header)
|
||||
|
||||
sub, err := cli.SubscribeNewHead(context.Background(), subCh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-timer.C: // FIXME: this event may be missed
|
||||
return errors.New("no result")
|
||||
case head := <-subCh:
|
||||
if container.GetProposer(head) == expectedAddress {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eth *ethereum) WaitForPeersConnected(expectedPeercount int) error {
|
||||
client := eth.NewClient()
|
||||
if client == nil {
|
||||
return errors.New("failed to retrieve client")
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ticker := time.NewTicker(time.Second * 1)
|
||||
defer ticker.Stop()
|
||||
for _ = range ticker.C {
|
||||
infos, err := client.AdminPeers(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(infos) < expectedPeercount {
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eth *ethereum) WaitForBlocks(num int, waitingTime ...time.Duration) error {
|
||||
return nil
|
||||
var first *big.Int
|
||||
|
||||
client := eth.NewClient()
|
||||
if client == nil {
|
||||
return errors.New("failed to retrieve client")
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var t time.Duration
|
||||
if len(waitingTime) > 0 {
|
||||
t = waitingTime[0]
|
||||
} else {
|
||||
t = 1 * time.Hour
|
||||
}
|
||||
|
||||
timeout := time.After(t)
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
return container.ErrNoBlock
|
||||
case <-ticker.C:
|
||||
n, err := client.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if first == nil {
|
||||
first = new(big.Int).Set(n)
|
||||
continue
|
||||
}
|
||||
// Check if new blocks are getting generated
|
||||
if new(big.Int).Sub(n, first).Int64() >= int64(num) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eth *ethereum) WaitForBlockHeight(num int) error {
|
||||
client := eth.NewClient()
|
||||
if client == nil {
|
||||
return errors.New("failed to retrieve client")
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for _ = range ticker.C {
|
||||
n, err := client.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n.Int64() >= int64(num) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eth *ethereum) WaitForNoBlocks(num int, duration time.Duration) error {
|
||||
return nil
|
||||
var first *big.Int
|
||||
|
||||
client := eth.NewClient()
|
||||
if client == nil {
|
||||
return errors.New("failed to retrieve client")
|
||||
}
|
||||
|
||||
timeout := time.After(duration)
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
n, err := client.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if first == nil {
|
||||
first = new(big.Int).Set(n)
|
||||
continue
|
||||
}
|
||||
// Check if new blocks are getting generated
|
||||
if new(big.Int).Sub(n, first).Int64() > int64(num) {
|
||||
return errors.New("generated more blocks than expected")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eth *ethereum) WaitForBalances(addrs []common.Address, duration ...time.Duration) error {
|
||||
client := eth.NewClient()
|
||||
if client == nil {
|
||||
return errors.New("failed to retrieve client")
|
||||
}
|
||||
|
||||
var t time.Duration
|
||||
if len(duration) > 0 {
|
||||
t = duration[0]
|
||||
} else {
|
||||
t = 1 * time.Hour
|
||||
}
|
||||
|
||||
waitBalance := func(addr common.Address) error {
|
||||
timeout := time.After(t)
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
return container.ErrTimeout
|
||||
case <-ticker.C:
|
||||
n, err := client.BalanceAt(context.Background(), addr, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if new blocks are getting generated
|
||||
if n.Uint64() <= 0 {
|
||||
continue
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errc := make(chan error, len(addrs))
|
||||
wg.Add(len(addrs))
|
||||
|
||||
for _, addr := range addrs {
|
||||
addr := addr
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errc <- waitBalance(addr)
|
||||
}()
|
||||
}
|
||||
// Wait for the first error, then terminate the others.
|
||||
var err error
|
||||
for i := 0; i < len(addrs); i++ {
|
||||
if err = <-errc; err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (eth *ethereum) AddPeer(address string) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -67,10 +67,20 @@ func Mine() Option {
|
|||
|
||||
func NodeKeyHex(hex string) Option {
|
||||
return func(eth *ethereum) {
|
||||
eth.nodekey = hex
|
||||
eth.args = append(eth.args, fmt.Sprintf("ethereum.nodekey.hex=%s", hex))
|
||||
}
|
||||
}
|
||||
|
||||
func TxPoolSize(size int) Option {
|
||||
return func(eth *ethereum) {
|
||||
eth.args = append(eth.args, fmt.Sprintf("benchmark.txpool.globalslots=%d", size))
|
||||
eth.args = append(eth.args, fmt.Sprintf("benchmark.txpool.accountslots=%d", size))
|
||||
eth.args = append(eth.args, fmt.Sprintf("benchmark.txpool.globalqueue=%d", size))
|
||||
eth.args = append(eth.args, fmt.Sprintf("benchmark.txpool.accountqueue=%d", size))
|
||||
}
|
||||
}
|
||||
|
||||
func Verbosity(verbosity int) Option {
|
||||
return func(eth *ethereum) {
|
||||
eth.args = append(eth.args, fmt.Sprintf("ethereum.verbosity=%d", verbosity))
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2017 AMIS Technologies
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
||||
istcommon "github.com/getamis/istanbul-tools/common"
|
||||
)
|
||||
|
||||
type RichMan interface {
|
||||
GiveEther(context.Context, []common.Address, *big.Int) error
|
||||
}
|
||||
|
||||
func (eth *ethereum) GiveEther(ctx context.Context, accounts []common.Address, amount *big.Int) error {
|
||||
client := eth.NewClient()
|
||||
if client == nil {
|
||||
return errors.New("failed to retrieve client")
|
||||
}
|
||||
|
||||
nonce, err := client.NonceAt(context.Background(), eth.Address(), nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to get nonce", "addr", eth.Address(), "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, account := range accounts {
|
||||
_ = istcommon.SendEther(client, eth.key, account, amount, nonce)
|
||||
nonce++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -78,7 +78,7 @@ func executeInParallel(fns ...func() error) error {
|
|||
var err error
|
||||
for i := 0; i < len(fns); i++ {
|
||||
if err = <-errc; err != nil {
|
||||
return err
|
||||
break
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
|
|
@ -52,7 +52,11 @@ var _ = Describe("Block synchronization testing", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
nodes, err = blockchain.CreateNodes(numberOfNodes,
|
||||
|
||||
incubator, ok := blockchain.(container.NodeIncubator)
|
||||
Expect(ok).To(BeTrue())
|
||||
|
||||
nodes, err = incubator.CreateNodes(numberOfNodes,
|
||||
container.ImageRepository("quay.io/amis/geth"),
|
||||
container.ImageTag("istanbul_develop"),
|
||||
container.DataDir("/data"),
|
||||
|
|
|
@ -17,19 +17,46 @@
|
|||
package load
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/getamis/istanbul-tools/charts"
|
||||
"github.com/getamis/istanbul-tools/common"
|
||||
|
||||
"github.com/getamis/istanbul-tools/tests"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/getamis/istanbul-tools/charts"
|
||||
"github.com/getamis/istanbul-tools/container"
|
||||
"github.com/getamis/istanbul-tools/k8s"
|
||||
"github.com/getamis/istanbul-tools/tests"
|
||||
)
|
||||
|
||||
var _ = Describe("TPS-01: Large amount of transactions", func() {
|
||||
|
||||
tests.CaseTable("with number of validators",
|
||||
func(numberOfValidators int) {
|
||||
var svcCharts []*charts.ValidatorServiceChart
|
||||
|
||||
BeforeSuite(func() {
|
||||
for i := 0; i < numberOfValidators; i++ {
|
||||
chart := charts.NewValidatorServiceChart(fmt.Sprintf("%d", i), nil)
|
||||
svcCharts = append(svcCharts, chart)
|
||||
|
||||
if err := chart.Install(false); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
AfterSuite(func() {
|
||||
for i := 0; i < numberOfValidators; i++ {
|
||||
svcCharts[i].Uninstall()
|
||||
}
|
||||
})
|
||||
|
||||
tests.CaseTable("with gas limit",
|
||||
func(gaslimit int) {
|
||||
tests.CaseTable("with txpool size",
|
||||
|
@ -45,41 +72,62 @@ var _ = Describe("TPS-01: Large amount of transactions", func() {
|
|||
tests.Case("21000*1000", 21000*1000),
|
||||
tests.Case("21000*3000", 21000*3000),
|
||||
)
|
||||
|
||||
},
|
||||
|
||||
tests.Case("4 validators", 4),
|
||||
tests.Case("7 validators", 7),
|
||||
tests.Case("10 validators", 10),
|
||||
)
|
||||
})
|
||||
|
||||
func runTests(numberOfValidators int, gaslimit int, txpoolSize int) {
|
||||
Describe("", func() {
|
||||
var (
|
||||
genesisChart tests.ChartInstaller
|
||||
staticNodesChart tests.ChartInstaller
|
||||
blockchain container.Blockchain
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
_, nodekeys, addrs := common.GenerateKeys(numberOfValidators)
|
||||
genesisChart = charts.NewGenesisChart(addrs, uint64(gaslimit))
|
||||
Expect(genesisChart.Install(false)).To(BeNil())
|
||||
blockchain = k8s.NewBlockchain(
|
||||
numberOfValidators,
|
||||
uint64(gaslimit),
|
||||
k8s.ImageRepository("quay.io/amis/geth"),
|
||||
k8s.ImageTag("istanbul_develop"),
|
||||
k8s.ServiceType("LoadBalancer"),
|
||||
k8s.Mine(),
|
||||
k8s.TxPoolSize(txpoolSize),
|
||||
)
|
||||
Expect(blockchain.Start(true)).To(BeNil())
|
||||
|
||||
staticNodesChart = charts.NewStaticNodesChart(nodekeys, common.GenerateIPs(len(nodekeys)))
|
||||
Expect(staticNodesChart.Install(false)).To(BeNil())
|
||||
tests.WaitFor(blockchain.Validators(), func(geth container.Ethereum, wg *sync.WaitGroup) {
|
||||
richman, ok := geth.(k8s.RichMan)
|
||||
Expect(ok).To(BeTrue())
|
||||
|
||||
var addrs []common.Address
|
||||
addr := common.HexToAddress("0x1a9afb711302c5f83b5902843d1c007a1a137632")
|
||||
addrs = append(addrs, addr)
|
||||
|
||||
// Give ether to all accounts
|
||||
err := richman.GiveEther(context.Background(), addrs, new(big.Int).Exp(big.NewInt(10), big.NewInt(24), nil))
|
||||
Expect(err).NotTo(BeNil())
|
||||
|
||||
err = geth.WaitForBalances(addrs, 10*time.Second)
|
||||
Expect(err).NotTo(BeNil())
|
||||
|
||||
wg.Done()
|
||||
})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(genesisChart.Uninstall()).To(BeNil())
|
||||
Expect(staticNodesChart.Uninstall()).To(BeNil())
|
||||
Expect(blockchain.Stop(true)).To(BeNil())
|
||||
blockchain.Finalize()
|
||||
})
|
||||
|
||||
It("", func() {
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func IstanbulLoadTest(t *testing.T) {
|
||||
func TestIstanbulLoadTesting(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Istanbul Load Test Suite")
|
||||
}
|
||||
|
|
|
@ -57,7 +57,11 @@ var _ = Describe("Block synchronization testing", func() {
|
|||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
nodes, err = blockchain.CreateNodes(numberOfNodes,
|
||||
|
||||
incubator, ok := blockchain.(container.NodeIncubator)
|
||||
Expect(ok).To(BeTrue())
|
||||
|
||||
nodes, err = incubator.CreateNodes(numberOfNodes,
|
||||
container.ImageRepository("quay.io/amis/geth"),
|
||||
container.ImageTag("istanbul_develop"),
|
||||
container.DataDir("/data"),
|
||||
|
|
Loading…
Reference in New Issue