mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-04 07:07:13 +00:00
Merge branch 'main' into feature/abci++vef
This commit is contained in:
+3
-3
@@ -21,7 +21,6 @@ const appVersion = 1
|
||||
// Application is an ABCI application for use by end-to-end tests. It is a
|
||||
// simple key/value store for strings, storing data in memory and persisting
|
||||
// to disk as JSON, taking state sync snapshots if requested.
|
||||
|
||||
type Application struct {
|
||||
abci.BaseApplication
|
||||
logger log.Logger
|
||||
@@ -89,7 +88,7 @@ func DefaultConfig(dir string) *Config {
|
||||
}
|
||||
|
||||
// NewApplication creates the application.
|
||||
func NewApplication(cfg *Config) (*Application, error) {
|
||||
func NewApplication(cfg *Config) (abci.Application, error) {
|
||||
state, err := NewState(cfg.Dir, cfg.PersistInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -271,7 +270,8 @@ func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) a
|
||||
}
|
||||
|
||||
func (app *Application) PrepareProposal(
|
||||
req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
|
||||
req abci.RequestPrepareProposal,
|
||||
) abci.ResponsePrepareProposal {
|
||||
txs := make([][]byte, 0, len(req.Txs))
|
||||
var totalBytes int64
|
||||
for _, tx := range req.Txs {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
// SyncApplication wraps an Application, managing its own synchronization. This
|
||||
// allows it to be called from an unsynchronized local client, as it is
|
||||
// implemented in a thread-safe way.
|
||||
type SyncApplication struct {
|
||||
mtx sync.RWMutex
|
||||
app *Application
|
||||
}
|
||||
|
||||
var _ abci.Application = (*SyncApplication)(nil)
|
||||
|
||||
func NewSyncApplication(cfg *Config) (abci.Application, error) {
|
||||
app, err := NewApplication(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SyncApplication{
|
||||
app: app.(*Application),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (app *SyncApplication) Info(req abci.RequestInfo) abci.ResponseInfo {
|
||||
app.mtx.RLock()
|
||||
defer app.mtx.RUnlock()
|
||||
return app.app.Info(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) InitChain(req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
return app.app.InitChain(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
|
||||
app.mtx.RLock()
|
||||
defer app.mtx.RUnlock()
|
||||
return app.app.CheckTx(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
|
||||
// app.app.PrepareProposal does not modify state
|
||||
app.mtx.RLock()
|
||||
defer app.mtx.RUnlock()
|
||||
return app.app.PrepareProposal(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal {
|
||||
// app.app.ProcessProposal does not modify state
|
||||
app.mtx.RLock()
|
||||
defer app.mtx.RUnlock()
|
||||
return app.app.ProcessProposal(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
return app.app.DeliverTx(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
return app.app.BeginBlock(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
return app.app.EndBlock(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) Commit() abci.ResponseCommit {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
return app.app.Commit()
|
||||
}
|
||||
|
||||
func (app *SyncApplication) Query(req abci.RequestQuery) abci.ResponseQuery {
|
||||
app.mtx.RLock()
|
||||
defer app.mtx.RUnlock()
|
||||
return app.app.Query(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
return app.app.ApplySnapshotChunk(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots {
|
||||
// Calls app.snapshots.List(), which is thread-safe.
|
||||
return app.app.ListSnapshots(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk {
|
||||
// Calls app.snapshots.LoadChunk, which is thread-safe.
|
||||
return app.app.LoadSnapshotChunk(req)
|
||||
}
|
||||
|
||||
func (app *SyncApplication) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot {
|
||||
app.mtx.Lock()
|
||||
defer app.mtx.Unlock()
|
||||
return app.app.OfferSnapshot(req)
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
// First we generate seed nodes, starting at the initial height.
|
||||
for i := 1; i <= numSeeds; i++ {
|
||||
manifest.Nodes[fmt.Sprintf("seed%02d", i)] = generateNode(
|
||||
r, e2e.ModeSeed, 0, manifest.InitialHeight, false)
|
||||
r, e2e.ModeSeed, false, 0, manifest.InitialHeight, false)
|
||||
}
|
||||
|
||||
// Next, we generate validators. We make sure a BFT quorum of validators start
|
||||
@@ -120,8 +120,12 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
nextStartAt += 5
|
||||
}
|
||||
name := fmt.Sprintf("validator%02d", i)
|
||||
syncApp := false
|
||||
if manifest.ABCIProtocol == string(e2e.ProtocolBuiltin) {
|
||||
syncApp = r.Intn(100) >= 50
|
||||
}
|
||||
manifest.Nodes[name] = generateNode(
|
||||
r, e2e.ModeValidator, startAt, manifest.InitialHeight, i <= 2)
|
||||
r, e2e.ModeValidator, syncApp, startAt, manifest.InitialHeight, i <= 2)
|
||||
|
||||
if startAt == 0 {
|
||||
(*manifest.Validators)[name] = int64(30 + r.Intn(71))
|
||||
@@ -149,8 +153,12 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
startAt = nextStartAt
|
||||
nextStartAt += 5
|
||||
}
|
||||
syncApp := false
|
||||
if manifest.ABCIProtocol == string(e2e.ProtocolBuiltin) {
|
||||
syncApp = r.Intn(100) >= 50
|
||||
}
|
||||
manifest.Nodes[fmt.Sprintf("full%02d", i)] = generateNode(
|
||||
r, e2e.ModeFull, startAt, manifest.InitialHeight, false)
|
||||
r, e2e.ModeFull, syncApp, startAt, manifest.InitialHeight, false)
|
||||
}
|
||||
|
||||
// We now set up peer discovery for nodes. Seed nodes are fully meshed with
|
||||
@@ -213,10 +221,11 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
// here, since we need to know the overall network topology and startup
|
||||
// sequencing.
|
||||
func generateNode(
|
||||
r *rand.Rand, mode e2e.Mode, startAt int64, initialHeight int64, forceArchive bool,
|
||||
r *rand.Rand, mode e2e.Mode, syncApp bool, startAt int64, initialHeight int64, forceArchive bool,
|
||||
) *e2e.ManifestNode {
|
||||
node := e2e.ManifestNode{
|
||||
Mode: string(mode),
|
||||
SyncApp: syncApp,
|
||||
StartAt: startAt,
|
||||
Database: nodeDatabases.Choose(r).(string),
|
||||
PrivvalProtocol: nodePrivvalProtocols.Choose(r).(string),
|
||||
|
||||
@@ -60,6 +60,7 @@ perturb = ["kill"]
|
||||
persistent_peers = ["validator01"]
|
||||
database = "rocksdb"
|
||||
abci_protocol = "builtin"
|
||||
sync_app = true
|
||||
perturb = ["pause"]
|
||||
|
||||
[node.validator05]
|
||||
|
||||
+10
-4
@@ -7,15 +7,17 @@ import (
|
||||
"github.com/BurntSushi/toml"
|
||||
|
||||
"github.com/tendermint/tendermint/test/e2e/app"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
)
|
||||
|
||||
// Config is the application configuration.
|
||||
type Config struct {
|
||||
ChainID string `toml:"chain_id"`
|
||||
Listen string
|
||||
Protocol string
|
||||
Dir string
|
||||
ChainID string `toml:"chain_id"`
|
||||
Listen string `toml:"listen"`
|
||||
Protocol string `toml:"protocol"`
|
||||
Dir string `toml:"dir"`
|
||||
Mode string `toml:"mode"`
|
||||
SyncApp bool `toml:"sync_app"`
|
||||
PersistInterval uint64 `toml:"persist_interval"`
|
||||
SnapshotInterval uint64 `toml:"snapshot_interval"`
|
||||
RetainBlocks uint64 `toml:"retain_blocks"`
|
||||
@@ -62,6 +64,10 @@ func (cfg Config) Validate() error {
|
||||
return errors.New("chain_id parameter is required")
|
||||
case cfg.Listen == "" && cfg.Protocol != "builtin":
|
||||
return errors.New("listen parameter is required")
|
||||
case cfg.SyncApp && cfg.Protocol != string(e2e.ProtocolBuiltin):
|
||||
return errors.New("sync_app parameter is only relevant for builtin applications")
|
||||
case cfg.SyncApp && cfg.Mode != string(e2e.ModeFull) && cfg.Mode != string(e2e.ModeValidator):
|
||||
return errors.New("sync_app parameter is only relevant to full nodes and validators")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
+17
-4
@@ -113,9 +113,22 @@ func startApp(cfg *Config) error {
|
||||
//
|
||||
// FIXME There is no way to simply load the configuration from a file, so we need to pull in Viper.
|
||||
func startNode(cfg *Config) error {
|
||||
app, err := app.NewApplication(cfg.App())
|
||||
if err != nil {
|
||||
return err
|
||||
var cc proxy.ClientCreator
|
||||
|
||||
if cfg.SyncApp {
|
||||
app, err := app.NewSyncApplication(cfg.App())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cc = proxy.NewUnsyncLocalClientCreator(app)
|
||||
logger.Info("Using synchronized app with unsynchronized local client")
|
||||
} else {
|
||||
app, err := app.NewApplication(cfg.App())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cc = proxy.NewLocalClientCreator(app)
|
||||
logger.Info("Using regular app with synchronized (regular) local client")
|
||||
}
|
||||
|
||||
tmcfg, nodeLogger, nodeKey, err := setupNode()
|
||||
@@ -126,7 +139,7 @@ func startNode(cfg *Config) error {
|
||||
n, err := node.NewNode(tmcfg,
|
||||
privval.LoadOrGenFilePV(tmcfg.PrivValidatorKeyFile(), tmcfg.PrivValidatorStateFile()),
|
||||
nodeKey,
|
||||
proxy.NewLocalClientCreator(app),
|
||||
cc,
|
||||
node.DefaultGenesisDocProviderFunc(tmcfg),
|
||||
node.DefaultDBProvider,
|
||||
node.DefaultMetricsProvider(tmcfg.Instrumentation),
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
"github.com/tendermint/tendermint/test/e2e/pkg/infra"
|
||||
)
|
||||
|
||||
var _ infra.Provider = &Provider{}
|
||||
|
||||
// Provider implements a docker-compose backed infrastructure provider.
|
||||
type Provider struct {
|
||||
Testnet *e2e.Testnet
|
||||
}
|
||||
|
||||
// Setup generates the docker-compose file and write it to disk, erroring if
|
||||
// any of these operations fail.
|
||||
func (p *Provider) Setup() error {
|
||||
compose, err := dockerComposeBytes(p.Testnet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//nolint: gosec
|
||||
// G306: Expect WriteFile permissions to be 0600 or less
|
||||
err = os.WriteFile(filepath.Join(p.Testnet.Dir, "docker-compose.yml"), compose, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dockerComposeBytes generates a Docker Compose config file for a testnet and returns the
|
||||
// file as bytes to be written out to disk.
|
||||
func dockerComposeBytes(testnet *e2e.Testnet) ([]byte, error) {
|
||||
// Must use version 2 Docker Compose format, to support IPv6.
|
||||
tmpl, err := template.New("docker-compose").Parse(`version: '2.4'
|
||||
networks:
|
||||
{{ .Name }}:
|
||||
labels:
|
||||
e2e: true
|
||||
driver: bridge
|
||||
{{- if .IPv6 }}
|
||||
enable_ipv6: true
|
||||
{{- end }}
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: {{ .IP }}
|
||||
|
||||
services:
|
||||
{{- range .Nodes }}
|
||||
{{ .Name }}:
|
||||
labels:
|
||||
e2e: true
|
||||
container_name: {{ .Name }}
|
||||
image: tendermint/e2e-node
|
||||
{{- if eq .ABCIProtocol "builtin" }}
|
||||
entrypoint: /usr/bin/entrypoint-builtin
|
||||
{{- end }}
|
||||
init: true
|
||||
ports:
|
||||
- 26656
|
||||
- {{ if .ProxyPort }}{{ .ProxyPort }}:{{ end }}26657
|
||||
- 6060
|
||||
volumes:
|
||||
- ./{{ .Name }}:/tendermint
|
||||
networks:
|
||||
{{ $.Name }}:
|
||||
ipv{{ if $.IPv6 }}6{{ else }}4{{ end}}_address: {{ .IP }}
|
||||
|
||||
{{end}}`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = tmpl.Execute(&buf, testnet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package infra
|
||||
|
||||
// Provider defines an API for manipulating the infrastructure of a
|
||||
// specific set of testnet infrastructure.
|
||||
type Provider interface {
|
||||
|
||||
// Setup generates any necessary configuration for the infrastructure
|
||||
// provider during testnet setup.
|
||||
Setup() error
|
||||
}
|
||||
|
||||
// NoopProvider implements the provider interface by performing noops for every
|
||||
// interface method. This may be useful if the infrastructure is managed by a
|
||||
// separate process.
|
||||
type NoopProvider struct {
|
||||
}
|
||||
|
||||
func (NoopProvider) Setup() error { return nil }
|
||||
|
||||
var _ Provider = NoopProvider{}
|
||||
@@ -0,0 +1,80 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
dockerIPv4CIDR = "10.186.73.0/24"
|
||||
dockerIPv6CIDR = "fd80:b10c::/48"
|
||||
|
||||
globalIPv4CIDR = "0.0.0.0/0"
|
||||
)
|
||||
|
||||
// InfrastructureData contains the relevant information for a set of existing
|
||||
// infrastructure that is to be used for running a testnet.
|
||||
type InfrastructureData struct {
|
||||
|
||||
// Provider is the name of infrastructure provider backing the testnet.
|
||||
// For example, 'docker' if it is running locally in a docker network or
|
||||
// 'digital-ocean', 'aws', 'google', etc. if it is from a cloud provider.
|
||||
Provider string `json:"provider"`
|
||||
|
||||
// Instances is a map of all of the machine instances on which to run
|
||||
// processes for a testnet.
|
||||
// The key of the map is the name of the instance, which each must correspond
|
||||
// to the names of one of the testnet nodes defined in the testnet manifest.
|
||||
Instances map[string]InstanceData `json:"instances"`
|
||||
|
||||
// Network is the CIDR notation range of IP addresses that all of the instances'
|
||||
// IP addresses are expected to be within.
|
||||
Network string `json:"network"`
|
||||
}
|
||||
|
||||
// InstanceData contains the relevant information for a machine instance backing
|
||||
// one of the nodes in the testnet.
|
||||
type InstanceData struct {
|
||||
IPAddress net.IP `json:"ip_address"`
|
||||
}
|
||||
|
||||
func NewDockerInfrastructureData(m Manifest) (InfrastructureData, error) {
|
||||
netAddress := dockerIPv4CIDR
|
||||
if m.IPv6 {
|
||||
netAddress = dockerIPv6CIDR
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(netAddress)
|
||||
if err != nil {
|
||||
return InfrastructureData{}, fmt.Errorf("invalid IP network address %q: %w", netAddress, err)
|
||||
}
|
||||
ipGen := newIPGenerator(ipNet)
|
||||
ifd := InfrastructureData{
|
||||
Provider: "docker",
|
||||
Instances: make(map[string]InstanceData),
|
||||
Network: netAddress,
|
||||
}
|
||||
for name := range m.Nodes {
|
||||
ifd.Instances[name] = InstanceData{
|
||||
IPAddress: ipGen.Next(),
|
||||
}
|
||||
}
|
||||
return ifd, nil
|
||||
}
|
||||
|
||||
func InfrastructureDataFromFile(p string) (InfrastructureData, error) {
|
||||
ifd := InfrastructureData{}
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return InfrastructureData{}, err
|
||||
}
|
||||
err = json.Unmarshal(b, &ifd)
|
||||
if err != nil {
|
||||
return InfrastructureData{}, err
|
||||
}
|
||||
if ifd.Network == "" {
|
||||
ifd.Network = globalIPv4CIDR
|
||||
}
|
||||
return ifd, nil
|
||||
}
|
||||
@@ -77,6 +77,15 @@ type ManifestNode struct {
|
||||
// is generated), and seed nodes run in seed mode with the PEX reactor enabled.
|
||||
Mode string `toml:"mode"`
|
||||
|
||||
// SyncApp specifies whether this node should use a synchronized application
|
||||
// with an unsynchronized local client. By default this is `false`, meaning
|
||||
// that the node will run an unsynchronized application with a synchronized
|
||||
// local client.
|
||||
//
|
||||
// Only applies to validators and full nodes where their ABCI protocol is
|
||||
// "builtin".
|
||||
SyncApp bool `toml:"sync_app"`
|
||||
|
||||
// Seeds is the list of node names to use as P2P seed nodes. Defaults to none.
|
||||
Seeds []string `toml:"seeds"`
|
||||
|
||||
|
||||
+15
-18
@@ -21,8 +21,6 @@ import (
|
||||
const (
|
||||
randomSeed int64 = 2308084734268
|
||||
proxyPortFirst uint32 = 5701
|
||||
networkIPv4 = "10.186.73.0/24"
|
||||
networkIPv6 = "fd80:b10c::/48"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -76,6 +74,7 @@ type Node struct {
|
||||
Name string
|
||||
Testnet *Testnet
|
||||
Mode Mode
|
||||
SyncApp bool // Should we use a synchronized app with an unsynchronized local client?
|
||||
PrivvalKey crypto.PrivKey
|
||||
NodeKey crypto.PrivKey
|
||||
IP net.IP
|
||||
@@ -100,37 +99,30 @@ type Node struct {
|
||||
// The testnet generation must be deterministic, since it is generated
|
||||
// separately by the runner and the test cases. For this reason, testnets use a
|
||||
// random seed to generate e.g. keys.
|
||||
func LoadTestnet(file string) (*Testnet, error) {
|
||||
func LoadTestnet(file string, ifd InfrastructureData) (*Testnet, error) {
|
||||
manifest, err := LoadManifest(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTestnetFromManifest(manifest, file)
|
||||
return NewTestnetFromManifest(manifest, file, ifd)
|
||||
}
|
||||
|
||||
// NewTestnetFromManifest creates and validates a testnet from a manifest
|
||||
func NewTestnetFromManifest(manifest Manifest, file string) (*Testnet, error) {
|
||||
func NewTestnetFromManifest(manifest Manifest, file string, ifd InfrastructureData) (*Testnet, error) {
|
||||
dir := strings.TrimSuffix(file, filepath.Ext(file))
|
||||
|
||||
// Set up resource generators. These must be deterministic.
|
||||
netAddress := networkIPv4
|
||||
if manifest.IPv6 {
|
||||
netAddress = networkIPv6
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(netAddress)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid IP network address %q: %w", netAddress, err)
|
||||
}
|
||||
|
||||
ipGen := newIPGenerator(ipNet)
|
||||
keyGen := newKeyGenerator(randomSeed)
|
||||
proxyPortGen := newPortGenerator(proxyPortFirst)
|
||||
_, ipNet, err := net.ParseCIDR(ifd.Network)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid IP network address %q: %w", ifd.Network, err)
|
||||
}
|
||||
|
||||
testnet := &Testnet{
|
||||
Name: filepath.Base(dir),
|
||||
File: file,
|
||||
Dir: dir,
|
||||
IP: ipGen.Network(),
|
||||
IP: ipNet,
|
||||
InitialHeight: 1,
|
||||
InitialState: manifest.InitialState,
|
||||
Validators: map[*Node]int64{},
|
||||
@@ -161,14 +153,19 @@ func NewTestnetFromManifest(manifest Manifest, file string) (*Testnet, error) {
|
||||
|
||||
for _, name := range nodeNames {
|
||||
nodeManifest := manifest.Nodes[name]
|
||||
ind, ok := ifd.Instances[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("information for node '%s' missing from infrastucture data", name)
|
||||
}
|
||||
node := &Node{
|
||||
Name: name,
|
||||
Testnet: testnet,
|
||||
PrivvalKey: keyGen.Generate(manifest.KeyType),
|
||||
NodeKey: keyGen.Generate("ed25519"),
|
||||
IP: ipGen.Next(),
|
||||
IP: ind.IPAddress,
|
||||
ProxyPort: proxyPortGen.Next(),
|
||||
Mode: ModeValidator,
|
||||
SyncApp: nodeManifest.SyncApp,
|
||||
Database: "goleveldb",
|
||||
ABCIProtocol: Protocol(testnet.ABCIProtocol),
|
||||
PrivvalProtocol: ProtocolFile,
|
||||
|
||||
+51
-5
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
"github.com/tendermint/tendermint/test/e2e/pkg/infra"
|
||||
"github.com/tendermint/tendermint/test/e2e/pkg/infra/docker"
|
||||
)
|
||||
|
||||
const randomSeed = 2308084734268
|
||||
@@ -26,6 +29,7 @@ type CLI struct {
|
||||
root *cobra.Command
|
||||
testnet *e2e.Testnet
|
||||
preserve bool
|
||||
infp infra.Provider
|
||||
}
|
||||
|
||||
// NewCLI sets up the CLI.
|
||||
@@ -41,19 +45,57 @@ func NewCLI() *CLI {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
testnet, err := e2e.LoadTestnet(file)
|
||||
m, err := e2e.LoadManifest(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inft, err := cmd.Flags().GetString("infrastructure-type")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ifd e2e.InfrastructureData
|
||||
switch inft {
|
||||
case "docker":
|
||||
var err error
|
||||
ifd, err = e2e.NewDockerInfrastructureData(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "digital-ocean":
|
||||
p, err := cmd.Flags().GetString("infrastructure-data")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p == "" {
|
||||
return errors.New("'--infrastructure-data' must be set when using the 'digital-ocean' infrastructure-type")
|
||||
}
|
||||
ifd, err = e2e.InfrastructureDataFromFile(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing infrastructure data: %s", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown infrastructure type '%s'", inft)
|
||||
}
|
||||
|
||||
testnet, err := e2e.LoadTestnet(m, file, ifd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading testnet: %s", err)
|
||||
}
|
||||
|
||||
cli.testnet = testnet
|
||||
cli.infp = &infra.NoopProvider{}
|
||||
if inft == "docker" {
|
||||
cli.infp = &docker.Provider{Testnet: testnet}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := Cleanup(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Setup(cli.testnet); err != nil {
|
||||
if err := Setup(cli.testnet, cli.infp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -118,6 +160,10 @@ func NewCLI() *CLI {
|
||||
cli.root.PersistentFlags().StringP("file", "f", "", "Testnet TOML manifest")
|
||||
_ = cli.root.MarkPersistentFlagRequired("file")
|
||||
|
||||
cli.root.PersistentFlags().StringP("infrastructure-type", "", "docker", "Backing infrastructure used to run the testnet. Either 'digital-ocean' or 'docker'")
|
||||
|
||||
cli.root.PersistentFlags().StringP("infrastructure-data", "", "", "path to the json file containing the infrastructure data. Only used if the 'infrastructure-type' is set to a value other than 'docker'")
|
||||
|
||||
cli.root.Flags().BoolVarP(&cli.preserve, "preserve", "p", false,
|
||||
"Preserves the running of the test net after tests are completed")
|
||||
|
||||
@@ -125,7 +171,7 @@ func NewCLI() *CLI {
|
||||
Use: "setup",
|
||||
Short: "Generates the testnet directory and configuration",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return Setup(cli.testnet)
|
||||
return Setup(cli.testnet, cli.infp)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -135,7 +181,7 @@ func NewCLI() *CLI {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
_, err := os.Stat(cli.testnet.Dir)
|
||||
if os.IsNotExist(err) {
|
||||
err = Setup(cli.testnet)
|
||||
err = Setup(cli.testnet, cli.infp)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -258,7 +304,7 @@ Does not run any perbutations.
|
||||
if err := Cleanup(cli.testnet); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Setup(cli.testnet); err != nil {
|
||||
if err := Setup(cli.testnet, cli.infp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
@@ -23,6 +21,7 @@ import (
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
e2e "github.com/tendermint/tendermint/test/e2e/pkg"
|
||||
"github.com/tendermint/tendermint/test/e2e/pkg/infra"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@@ -39,7 +38,7 @@ const (
|
||||
)
|
||||
|
||||
// Setup sets up the testnet configuration.
|
||||
func Setup(testnet *e2e.Testnet) error {
|
||||
func Setup(testnet *e2e.Testnet, infp infra.Provider) error {
|
||||
logger.Info("setup", "msg", log.NewLazySprintf("Generating testnet files in %q", testnet.Dir))
|
||||
|
||||
err := os.MkdirAll(testnet.Dir, os.ModePerm)
|
||||
@@ -47,11 +46,7 @@ func Setup(testnet *e2e.Testnet) error {
|
||||
return err
|
||||
}
|
||||
|
||||
compose, err := MakeDockerCompose(testnet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(testnet.Dir, "docker-compose.yml"), compose, 0o644) //nolint:gosec
|
||||
err = infp.Setup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -126,70 +121,6 @@ func Setup(testnet *e2e.Testnet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeDockerCompose generates a Docker Compose config for a testnet.
|
||||
func MakeDockerCompose(testnet *e2e.Testnet) ([]byte, error) {
|
||||
// Must use version 2 Docker Compose format, to support IPv6.
|
||||
tmpl, err := template.New("docker-compose").Funcs(template.FuncMap{
|
||||
"misbehaviorsToString": func(misbehaviors map[int64]string) string {
|
||||
str := ""
|
||||
for height, misbehavior := range misbehaviors {
|
||||
// after the first behavior set, a comma must be prepended
|
||||
if str != "" {
|
||||
str += ","
|
||||
}
|
||||
heightString := strconv.Itoa(int(height))
|
||||
str += misbehavior + "," + heightString
|
||||
}
|
||||
return str
|
||||
},
|
||||
}).Parse(`version: '2.4'
|
||||
|
||||
networks:
|
||||
{{ .Name }}:
|
||||
labels:
|
||||
e2e: true
|
||||
driver: bridge
|
||||
{{- if .IPv6 }}
|
||||
enable_ipv6: true
|
||||
{{- end }}
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: {{ .IP }}
|
||||
|
||||
services:
|
||||
{{- range .Nodes }}
|
||||
{{ .Name }}:
|
||||
labels:
|
||||
e2e: true
|
||||
container_name: {{ .Name }}
|
||||
image: tendermint/e2e-node
|
||||
{{- if eq .ABCIProtocol "builtin" }}
|
||||
entrypoint: /usr/bin/entrypoint-builtin
|
||||
{{- end }}
|
||||
init: true
|
||||
ports:
|
||||
- 26656
|
||||
- {{ if .ProxyPort }}{{ .ProxyPort }}:{{ end }}26657
|
||||
- 6060
|
||||
volumes:
|
||||
- ./{{ .Name }}:/tendermint
|
||||
networks:
|
||||
{{ $.Name }}:
|
||||
ipv{{ if $.IPv6 }}6{{ else }}4{{ end}}_address: {{ .IP }}
|
||||
|
||||
{{end}}`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = tmpl.Execute(&buf, testnet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// MakeGenesis generates a genesis document.
|
||||
func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) {
|
||||
genesis := types.GenesisDoc{
|
||||
@@ -327,6 +258,7 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) {
|
||||
"dir": "data/app",
|
||||
"listen": AppAddressUNIX,
|
||||
"mode": node.Mode,
|
||||
"sync_app": node.SyncApp,
|
||||
"proxy_port": node.ProxyPort,
|
||||
"protocol": "socket",
|
||||
"persist_interval": node.PersistInterval,
|
||||
|
||||
@@ -66,23 +66,27 @@ func testNode(t *testing.T, testFunc func(*testing.T, e2e.Node)) {
|
||||
func loadTestnet(t *testing.T) e2e.Testnet {
|
||||
t.Helper()
|
||||
|
||||
manifest := os.Getenv("E2E_MANIFEST")
|
||||
if manifest == "" {
|
||||
manifestFile := os.Getenv("E2E_MANIFEST")
|
||||
if manifestFile == "" {
|
||||
t.Skip("E2E_MANIFEST not set, not an end-to-end test run")
|
||||
}
|
||||
if !filepath.IsAbs(manifest) {
|
||||
manifest = filepath.Join("..", manifest)
|
||||
if !filepath.IsAbs(manifestFile) {
|
||||
manifestFile = filepath.Join("..", manifestFile)
|
||||
}
|
||||
|
||||
testnetCacheMtx.Lock()
|
||||
defer testnetCacheMtx.Unlock()
|
||||
if testnet, ok := testnetCache[manifest]; ok {
|
||||
if testnet, ok := testnetCache[manifestFile]; ok {
|
||||
return testnet
|
||||
}
|
||||
|
||||
testnet, err := e2e.LoadTestnet(manifest)
|
||||
m, err := e2e.LoadManifest(manifestFile)
|
||||
require.NoError(t, err)
|
||||
testnetCache[manifest] = *testnet
|
||||
ifd, err := e2e.NewDockerInfrastructureData(m)
|
||||
require.NoError(t, err)
|
||||
|
||||
testnet, err := e2e.LoadTestnet(m, manifestFile, ifd)
|
||||
require.NoError(t, err)
|
||||
testnetCache[manifestFile] = *testnet
|
||||
return *testnet
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ func FuzzRPCJSONRPCServer(f *testing.F) {
|
||||
I int `json:"i"`
|
||||
}
|
||||
var rpcFuncMap = map[string]*rpcserver.RPCFunc{
|
||||
"c": rpcserver.NewRPCFunc(func(ctx *rpctypes.Context, args *args) (string, error) {
|
||||
"c": rpcserver.NewRPCFunc(func(ctx *rpctypes.Context, args *args, options ...rpcserver.Option) (string, error) {
|
||||
return "foo", nil
|
||||
}, "args"),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package payload
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
const keyPrefix = "a="
|
||||
const maxPayloadSize = 4 * 1024 * 1024
|
||||
|
||||
// NewBytes generates a new payload and returns the encoded representation of
|
||||
// the payload as a slice of bytes. NewBytes uses the fields on the Options
|
||||
@@ -25,10 +26,16 @@ func NewBytes(p *Payload) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.Size < uint64(us) {
|
||||
return nil, fmt.Errorf("configured size %d not large enough to fit unpadded transaction of size %d", p.Size, us)
|
||||
if p.Size > maxPayloadSize {
|
||||
return nil, fmt.Errorf("configured size %d is too large (>%d)", p.Size, maxPayloadSize)
|
||||
}
|
||||
p.Padding = make([]byte, p.Size-uint64(us))
|
||||
pSize := int(p.Size) // #nosec -- The "if" above makes this cast safe
|
||||
if pSize < us {
|
||||
return nil, fmt.Errorf("configured size %d not large enough to fit unpadded transaction of size %d", pSize, us)
|
||||
}
|
||||
|
||||
// We halve the padding size because we transform the TX to hex
|
||||
p.Padding = make([]byte, (pSize-us)/2)
|
||||
_, err = rand.Read(p.Padding)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -37,22 +44,28 @@ func NewBytes(p *Payload) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := []byte(hex.EncodeToString(b))
|
||||
|
||||
// prepend a single key so that the kv store only ever stores a single
|
||||
// transaction instead of storing all tx and ballooning in size.
|
||||
return append([]byte(keyPrefix), b...), nil
|
||||
return append([]byte(keyPrefix), h...), nil
|
||||
}
|
||||
|
||||
// FromBytes extracts a paylod from the byte representation of the payload.
|
||||
// FromBytes leaves the padding untouched, returning it to the caller to handle
|
||||
// or discard per their preference.
|
||||
func FromBytes(b []byte) (*Payload, error) {
|
||||
p := &Payload{}
|
||||
tr := bytes.TrimPrefix(b, []byte(keyPrefix))
|
||||
if bytes.Equal(b, tr) {
|
||||
return nil, errors.New("payload bytes missing key prefix")
|
||||
trH := bytes.TrimPrefix(b, []byte(keyPrefix))
|
||||
if bytes.Equal(b, trH) {
|
||||
return nil, fmt.Errorf("payload bytes missing key prefix '%s'", keyPrefix)
|
||||
}
|
||||
err := proto.Unmarshal(tr, p)
|
||||
trB, err := hex.DecodeString(string(trH))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &Payload{}
|
||||
err = proto.Unmarshal(trB, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -83,5 +96,6 @@ func CalculateUnpaddedSize(p *Payload) (int, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b) + len(keyPrefix), nil
|
||||
h := []byte(hex.EncodeToString(b))
|
||||
return len(h) + len(keyPrefix), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user