refactor(testutil): remove unused api (#17067)

This commit is contained in:
Julien Robert 2023-07-20 00:53:39 +02:00 committed by GitHub
parent 93c44d9da7
commit 75501a16e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 0 additions and 1631 deletions

View File

@ -1,100 +0,0 @@
package testnet
import (
"fmt"
"runtime/debug"
"sync"
"github.com/cometbft/cometbft/node"
)
// CometBFT v0.37 uses a singleton to manage the RPC "environment".
// v0.38 will not have that restriction, which was removed in commit:
// https://github.com/cometbft/cometbft/commit/3324f49fb7e7b40189726746493e83b82a61b558
//
// We manage a corresponding global lock to ensure
// we don't attempt to use multiple active RPC servers in one process,
// which would result in unpredictable or incorrect behavior.
// Once the SDK adopts Comet v0.38+, we can remove this global lock mechanism.
// Our singleton complementing Comet's global RPC state.
var globalCometMu = new(cometRPCMutex)
type cometRPCMutex struct {
mu sync.Mutex
prevLockStack []byte
}
// CometRPCInUseError is returned on a failure to acquire
// the global comet RPC lock.
//
// This type will be removed once the Cosmos SDK adopts CometBFT v0.38 or newer.
type CometRPCInUseError struct {
prevStack []byte
}
func (e CometRPCInUseError) Error() string {
return fmt.Sprintf(`Failed to acquire global lock for Comet RPC servers.
If this in a test using t.Parallel(), remove the call to t.Parallel().
If this is in a test NOT using t.Parallel,
ensure that other callers call both Stop() and Wait() on the nodes.
If there are multiple comet instances in one test using RPC servers,
ensure that only one instance has the RPC listener enabled.
These restrictions will be loosened once cosmos-sdk adopts comet-bft v0.38 or newer.
Stack where lock was previously acquired:
%s
`, e.prevStack)
}
// Acquire attempts to acquire the underlying mutex.
// If it cannot be acquired on the first attempt,
// Acquire returns a [CometRPCInUseError] value.
func (m *cometRPCMutex) Acquire() error {
if !m.mu.TryLock() {
// If we can't acquire the lock,
// there is another active comet node using RPC.
//
// This was initially going to be a panic,
// but we can't easily write tests against that since
// the panic occurs in a separate goroutine
// when called through NewNetwork.
//
// Note, reading m.prevLockStack without holding m.mu
// is technically a data race,
// since it is possible that the previous caller was about to unlock.
// Nonetheless, the programmer is responsible for avoiding that situation,
// and a data race during a failure isn't particularly concerning.
return CometRPCInUseError{prevStack: m.prevLockStack}
}
// Now we hold the lock, so first record the stack when the lock was taken.
m.prevLockStack = debug.Stack()
return nil
}
// Release unlocks m depending on n.
// If n is nil, m is unlocked immediately.
// If n is not nil, a new goroutine is created
// and n is released after the node has finished running.
func (m *cometRPCMutex) Release(n *node.Node) {
if n == nil {
m.prevLockStack = nil
m.mu.Unlock()
return
}
go m.releaseAfterWait(n)
}
func (m *cometRPCMutex) releaseAfterWait(n *node.Node) {
n.Wait()
m.prevLockStack = nil
m.mu.Unlock()
}

View File

@ -1,248 +0,0 @@
package testnet
import (
"errors"
"fmt"
"net"
"os"
"path/filepath"
"syscall"
cmtcfg "github.com/cometbft/cometbft/config"
cmted25519 "github.com/cometbft/cometbft/crypto/ed25519"
"github.com/cometbft/cometbft/node"
"github.com/cometbft/cometbft/p2p"
"github.com/cometbft/cometbft/privval"
"github.com/cometbft/cometbft/proxy"
cmttypes "github.com/cometbft/cometbft/types"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/server"
servercmtlog "github.com/cosmos/cosmos-sdk/server/log"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
)
// CometStarter offers a builder-pattern interface to
// starting a Comet instance with an ABCI application running alongside.
//
// As CometStart is more broadly used in the codebase,
// the number of available methods on CometStarter will grow.
type CometStarter struct {
logger log.Logger
app servertypes.ABCI
cfg *cmtcfg.Config
valPrivKey cmted25519.PrivKey
genesis []byte
rootDir string
rpcListen bool
tcpAddrChooser func() string
startTries int
}
// NewCometStarter accepts a minimal set of arguments to start comet with an ABCI app.
// For further configuration, chain other CometStarter methods before calling Start:
//
// NewCometStarter(...).Logger(...).Start()
func NewCometStarter(
app servertypes.ABCI,
cfg *cmtcfg.Config,
valPrivKey cmted25519.PrivKey,
genesis []byte,
rootDir string,
) *CometStarter {
cfg.SetRoot(rootDir)
// CometStarter won't work without these settings,
// so set them unconditionally.
cfg.P2P.AllowDuplicateIP = true
cfg.P2P.AddrBookStrict = false
// For now, we disallow RPC listening.
// Comet v0.37 uses a global value such that multiple comet nodes in one process
// end up contending over one "rpc environment" and only the last-started validator
// will control the RPC service.
//
// The "rpc environment" was removed as a global in
// https://github.com/cometbft/cometbft/commit/3324f49fb7e7b40189726746493e83b82a61b558
// which is due to land in v0.38.
//
// At that point, we should keep the default as RPC off,
// but we should add a RPCListen method to opt in to enabling it.
// If RPC.ListenAddress is the default value, clear it.
const defaultRPCListenAddr = "tcp://127.0.0.1:26657"
if cfg.RPC.ListenAddress == defaultRPCListenAddr {
cfg.RPC.ListenAddress = ""
}
// Then if it was set to anything other than empty or the default value,
// fail with a clear explanation on how to enable RPC.
// The RPCListen method must be used in order to correctly pick an available listen address.
if cfg.RPC.ListenAddress != "" {
panic(fmt.Errorf("NewCometStarter: cfg.RPC.ListenAddress must be empty (but was %q); use (*CometStarter).RPCListen() instead", cfg.RPC.ListenAddress))
}
// defaultStartTries is somewhat arbitrary.
// Occasionally TestCometStarter_PortContention would fail with 10 tries,
// and bumping it up to 12 makes it almost never fail.
const defaultStartTries = 12
return &CometStarter{
logger: log.NewNopLogger(),
app: app,
cfg: cfg,
genesis: genesis,
valPrivKey: valPrivKey,
rootDir: rootDir,
startTries: defaultStartTries,
}
}
// Logger sets the logger for s and for the eventual started comet instance.
func (s *CometStarter) Logger(logger log.Logger) *CometStarter {
s.logger = logger
return s
}
// RPCListen enables the RPC listener service on the underlying Comet node.
// The RPC service must be enabled this way so that s can choose a dynamic port,
// retrying if necessary.
//
// Note that there is a limitation in CometBFT v0.37 that
// prevents more than one RPC server running at a time.
// Once the Cosmos SDK has adopted CometBFT v0.38 or newer,
// that limitation will be removed.
func (s *CometStarter) RPCListen() *CometStarter {
s.rpcListen = true
return s
}
// Start returns a started Comet node.
func (s *CometStarter) Start() (n *node.Node, err error) {
if s.rpcListen {
if err := globalCometMu.Acquire(); err != nil {
return nil, err
}
// Wrap this defer in an anonymous function so we don't immediately evaluate
// n, which would always be nil at this point.
defer func() {
globalCometMu.Release(n)
}()
}
fpv, nodeKey, err := s.initDisk()
if err != nil {
return nil, err
}
appGenesisProvider := func() (*cmttypes.GenesisDoc, error) {
appGenesis, err := genutiltypes.AppGenesisFromFile(s.cfg.GenesisFile())
if err != nil {
return nil, err
}
return appGenesis.ToGenesisDoc()
}
cmtApp := server.NewCometABCIWrapper(s.app)
for i := 0; i < s.startTries; i++ {
s.cfg.P2P.ListenAddress = s.likelyAvailableAddress()
if s.rpcListen {
s.cfg.RPC.ListenAddress = s.likelyAvailableAddress()
}
n, err := node.NewNode(
s.cfg,
fpv,
nodeKey,
proxy.NewLocalClientCreator(cmtApp),
appGenesisProvider,
cmtcfg.DefaultDBProvider,
node.DefaultMetricsProvider(s.cfg.Instrumentation),
servercmtlog.CometLoggerWrapper{Logger: s.logger},
)
if err != nil {
return nil, fmt.Errorf("failed to create comet node: %w", err)
}
err = n.Start()
if err == nil {
return n, nil
}
// Error isn't nil -- if it is EADDRINUSE then we can try again.
if errors.Is(err, syscall.EADDRINUSE) {
continue
}
// Non-nil error that isn't EADDRINUSE, just return the error.
return nil, err
}
// If we didn't return a node from inside the loop,
// then we must have exhausted our try limit.
return nil, fmt.Errorf("failed to start a comet node within %d tries", s.startTries)
}
// initDisk creates the config and data directories on disk,
// and other required files, so that comet and the validator work correctly.
// It also generates a node key for validators.
func (s *CometStarter) initDisk() (cmttypes.PrivValidator, *p2p.NodeKey, error) {
if err := os.MkdirAll(filepath.Join(s.rootDir, "config"), 0o750); err != nil {
return nil, nil, fmt.Errorf("failed to make config directory: %w", err)
}
if err := os.MkdirAll(filepath.Join(s.rootDir, "data"), 0o750); err != nil {
return nil, nil, fmt.Errorf("failed to make data directory: %w", err)
}
fpv := privval.NewFilePV(s.valPrivKey, s.cfg.PrivValidatorKeyFile(), s.cfg.PrivValidatorStateFile())
fpv.Save()
if err := os.WriteFile(s.cfg.GenesisFile(), s.genesis, 0o600); err != nil {
return nil, nil, fmt.Errorf("failed to write genesis file: %w", err)
}
nodeKey, err := p2p.LoadOrGenNodeKey(s.cfg.NodeKeyFile())
if err != nil {
return nil, nil, err
}
return fpv, nodeKey, nil
}
// TCPAddrChooser sets the function to use when selecting a (likely to be free)
// TCP address for comet's P2P port.
//
// This should only be used when testing CometStarter.
//
// It must return a string in format "tcp://IP:PORT".
func (s *CometStarter) TCPAddrChooser(fn func() string) *CometStarter {
s.tcpAddrChooser = fn
return s
}
// likelyAvailableAddress provides a TCP address that is likely to be available
// for comet or other processes to listen on.
//
// Generally, it is better to directly provide a net.Listener that is already bound to an address,
// but unfortunately comet does not offer that as part of its API.
// Instead, we locally bind to :0 and then report that as a "likely available" port.
// If another process steals that port before our comet instance can bind to it,
// the Start method handles retries.
func (s *CometStarter) likelyAvailableAddress() string {
// If s.TCPAddrChooser was called, use that implementation.
if s.tcpAddrChooser != nil {
return s.tcpAddrChooser()
}
// Fall back to attempting a random port.
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
panic(fmt.Errorf("failed to bind to random port: %w", err))
}
defer ln.Close()
return "tcp://" + ln.Addr().String()
}

View File

@ -1,59 +0,0 @@
package testnet
import (
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
// DelegatorPrivKeys is a slice of secp256k1.PrivKey.
type DelegatorPrivKeys []*secp256k1.PrivKey
// NewDelegatorPrivKeysreturns a DelegatorPrivKeys of length n,
// where each set of keys is dynamically generated.
func NewDelegatorPrivKeys(n int) DelegatorPrivKeys {
dpk := make(DelegatorPrivKeys, n)
for i := range dpk {
dpk[i] = secp256k1.GenPrivKey()
}
return dpk
}
// BaseAccounts returns the base accounts corresponding to the delegators' public keys.
func (dpk DelegatorPrivKeys) BaseAccounts() BaseAccounts {
ba := make(BaseAccounts, len(dpk))
for i, pk := range dpk {
pubKey := pk.PubKey()
const accountNumber = 0
const sequenceNumber = 0
ba[i] = authtypes.NewBaseAccount(
pubKey.Address().Bytes(), pubKey, accountNumber, sequenceNumber,
)
}
return ba
}
// BaseAccounts is a slice of [*authtypes.BaseAccount].
type BaseAccounts []*authtypes.BaseAccount
// Balances creates a slice of [banktypes.Balance] for each account in ba,
// where each balance has an identical Coins value of the singleBalance argument.
func (ba BaseAccounts) Balances(singleBalance sdk.Coins) []banktypes.Balance {
balances := make([]banktypes.Balance, len(ba))
for i, b := range ba {
balances[i] = banktypes.Balance{
Address: b.GetAddress().String(),
Coins: singleBalance,
}
}
return balances
}

View File

@ -1,4 +0,0 @@
// Package testnet provides APIs for easily create and configure
// validators, genesis files, and comet instances,
// to support testing app chain instances in-process.
package testnet

View File

@ -1,527 +0,0 @@
package testnet
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
cmttypes "github.com/cometbft/cometbft/types"
"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
// GenesisBuilder enables constructing a genesis file,
// following a builder pattern.
//
// None of the methods on GenesisBuilder return an error,
// choosing instead to panic.
// GenesisBuilder is only intended for use in tests,
// where inputs are predetermined and expected to succeed.
type GenesisBuilder struct {
amino *codec.LegacyAmino
codec *codec.ProtoCodec
// The value used in ChainID.
// Some other require this value,
// so store it as a field instead of re-parsing it from JSON.
chainID string
// The outer JSON object.
// Most data goes into app_state, but there are some top-level fields.
outer map[string]json.RawMessage
// Many of GenesisBuilder's methods operate on the app_state JSON object,
// so we track that separately and nest it inside outer upon a call to JSON().
appState map[string]json.RawMessage
gentxs []sdk.Tx
}
// NewGenesisBuilder returns an initialized GenesisBuilder.
//
// The returned GenesisBuilder has an initial height of 1
// and a genesis_time of the current time when the function was called.
func NewGenesisBuilder() *GenesisBuilder {
ir := codectypes.NewInterfaceRegistry()
cryptocodec.RegisterInterfaces(ir)
stakingtypes.RegisterInterfaces(ir)
banktypes.RegisterInterfaces(ir)
authtypes.RegisterInterfaces(ir)
pCodec := codec.NewProtoCodec(ir)
return &GenesisBuilder{
amino: codec.NewLegacyAmino(),
codec: pCodec,
outer: map[string]json.RawMessage{
"initial_height": json.RawMessage(`"1"`),
"genesis_time": json.RawMessage(
strconv.AppendQuote(nil, time.Now().UTC().Format(time.RFC3339Nano)),
),
},
appState: map[string]json.RawMessage{},
}
}
// GenTx emulates the gentx CLI, creating a message to create a validator
// represented by val, with "amount" self delegation,
// and signed by privVal.
func (b *GenesisBuilder) GenTx(privVal secp256k1.PrivKey, val cmttypes.GenesisValidator, amount sdk.Coin) *GenesisBuilder {
if b.chainID == "" {
panic(fmt.Errorf("(*GenesisBuilder).GenTx must not be called before (*GenesisBuilder).ChainID"))
}
pubKey, err := cryptocodec.FromCmtPubKeyInterface(val.PubKey)
if err != nil {
panic(err)
}
// Produce the create validator message.
msg, err := stakingtypes.NewMsgCreateValidator(
privVal.PubKey().Address().Bytes(),
pubKey,
amount,
stakingtypes.Description{
Moniker: "TODO",
},
stakingtypes.CommissionRates{
Rate: math.LegacyMustNewDecFromStr("0.1"),
MaxRate: math.LegacyMustNewDecFromStr("0.2"),
MaxChangeRate: math.LegacyMustNewDecFromStr("0.01"),
},
math.OneInt(),
)
if err != nil {
panic(err)
}
if err := msg.Validate(); err != nil {
panic(err)
}
txConf := authtx.NewTxConfig(b.codec, authtx.DefaultSignModes)
txb := txConf.NewTxBuilder()
if err := txb.SetMsgs(msg); err != nil {
panic(err)
}
const signMode = signing.SignMode_SIGN_MODE_DIRECT
// Need to set the signature object on the tx builder first,
// otherwise we end up signing a different total message
// compared to what gets eventually verified.
if err := txb.SetSignatures(
signing.SignatureV2{
PubKey: privVal.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: signMode,
},
},
); err != nil {
panic(err)
}
// Generate bytes to be signed.
bytesToSign, err := authsigning.GetSignBytesAdapter(
context.Background(),
txConf.SignModeHandler(),
signing.SignMode_SIGN_MODE_DIRECT,
authsigning.SignerData{
ChainID: b.chainID,
PubKey: privVal.PubKey(),
Address: sdk.MustBech32ifyAddressBytes("cosmos", privVal.PubKey().Address()), // TODO: don't hardcode cosmos1!
// No account or sequence number for gentx.
},
txb.GetTx(),
)
if err != nil {
panic(err)
}
// Produce the signature.
signed, err := privVal.Sign(bytesToSign)
if err != nil {
panic(err)
}
// Set the signature on the builder.
if err := txb.SetSignatures(
signing.SignatureV2{
PubKey: privVal.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: signMode,
Signature: signed,
},
},
); err != nil {
panic(err)
}
b.gentxs = append(b.gentxs, txb.GetTx())
return b
}
// ChainID sets the genesis's "chain_id" field.
func (b *GenesisBuilder) ChainID(id string) *GenesisBuilder {
b.chainID = id
var err error
b.outer["chain_id"], err = json.Marshal(id)
if err != nil {
panic(err)
}
return b
}
// GenesisTime sets the genesis's "genesis_time" field.
// Note that [NewGenesisBuilder] sets the genesis time to the current time by default.
func (b *GenesisBuilder) GenesisTime(t time.Time) *GenesisBuilder {
var err error
b.outer["genesis_time"], err = json.Marshal(t.Format(time.RFC3339Nano))
if err != nil {
panic(err)
}
return b
}
// InitialHeight sets the genesis's "initial_height" field to h.
// Note that [NewGenesisBuilder] sets the initial height to 1 by default.
func (b *GenesisBuilder) InitialHeight(h int64) *GenesisBuilder {
var err error
b.outer["initial_height"], err = json.Marshal(strconv.FormatInt(h, 10))
if err != nil {
panic(err)
}
return b
}
// AuthParams sets the auth params on the genesis.
func (b *GenesisBuilder) AuthParams(params authtypes.Params) *GenesisBuilder {
var err error
b.appState[authtypes.ModuleName], err = json.Marshal(map[string]any{
"params": params,
})
if err != nil {
panic(err)
}
return b
}
// DefaultAuthParams calls b.AuthParams with [authtypes.DefaultParams],
// as a convenience so that callers do not have to import the authtypes package.
func (b *GenesisBuilder) DefaultAuthParams() *GenesisBuilder {
return b.AuthParams(authtypes.DefaultParams())
}
// Consensus sets the consensus parameters and initial validators.
//
// If params is nil, [cmttypes.DefaultConsensusParams] is used.
func (b *GenesisBuilder) Consensus(params *cmttypes.ConsensusParams, vals CometGenesisValidators) *GenesisBuilder {
if params == nil {
params = cmttypes.DefaultConsensusParams()
}
var err error
b.outer[consensusparamtypes.ModuleName], err = (&genutiltypes.ConsensusGenesis{
Params: params,
Validators: vals.ToComet(),
}).MarshalJSON()
if err != nil {
panic(err)
}
return b
}
// Staking sets the staking parameters, validators, and delegations on the genesis.
//
// This also modifies the bank state's balances to include the bonded pool balance.
func (b *GenesisBuilder) Staking(
params stakingtypes.Params,
vals StakingValidators,
delegations []stakingtypes.Delegation,
) *GenesisBuilder {
var err error
b.appState[stakingtypes.ModuleName], err = b.codec.MarshalJSON(
stakingtypes.NewGenesisState(params, vals.ToStakingType(), delegations),
)
if err != nil {
panic(err)
}
// Modify bank state for bonded pool.
var coins sdk.Coins
for _, v := range vals {
coins = coins.Add(sdk.NewCoin(sdk.DefaultBondDenom, v.V.Tokens))
}
bondedPoolBalance := banktypes.Balance{
Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
Coins: coins,
}
// get bank types genesis, add account
bankGenesis := banktypes.GetGenesisStateFromAppState(b.codec, b.appState)
bankGenesis.Balances = append(bankGenesis.Balances, bondedPoolBalance)
b.appState[banktypes.ModuleName], err = b.codec.MarshalJSON(bankGenesis)
if err != nil {
panic(err)
}
return b
}
// StakingWithDefaultParams calls b.Staking, providing [stakingtypes.DefaultParams]
// so that callers don't necessarily have to import [stakingtypes].
func (b *GenesisBuilder) StakingWithDefaultParams(vals StakingValidators, delegations []stakingtypes.Delegation) *GenesisBuilder {
return b.Staking(stakingtypes.DefaultParams(), vals, delegations)
}
// DefaultStaking is shorthand for b.StakingWithDefaultParams with nil validators and delegations.
func (b *GenesisBuilder) DefaultStaking() *GenesisBuilder {
return b.StakingWithDefaultParams(nil, nil)
}
// Banking sets the banking genesis state.
func (b *GenesisBuilder) Banking(
params banktypes.Params,
balances []banktypes.Balance,
totalSupply sdk.Coins,
denomMetadata []banktypes.Metadata,
sendEnabled []banktypes.SendEnabled,
) *GenesisBuilder {
var err error
b.appState[banktypes.ModuleName], err = b.codec.MarshalJSON(
banktypes.NewGenesisState(
params,
balances,
totalSupply,
denomMetadata,
sendEnabled,
),
)
if err != nil {
panic(err)
}
return b
}
// BankingWithDefaultParams calls b.Banking with [banktypes.DefaultParams],
// so that callers don't necessarily have to import [banktypes].
func (b *GenesisBuilder) BankingWithDefaultParams(
balances []banktypes.Balance,
totalSupply sdk.Coins,
denomMetadata []banktypes.Metadata,
sendEnabled []banktypes.SendEnabled,
) *GenesisBuilder {
return b.Banking(
banktypes.DefaultParams(),
balances,
totalSupply,
denomMetadata,
sendEnabled,
)
}
// Mint sets the mint genesis state.
func (b *GenesisBuilder) Mint(m minttypes.Minter, p minttypes.Params) *GenesisBuilder {
var err error
b.appState[minttypes.ModuleName], err = b.codec.MarshalJSON(
minttypes.NewGenesisState(m, p),
)
if err != nil {
panic(err)
}
return b
}
// DefaultMint calls b.Mint with [minttypes.DefaultInitialMinter] and [minttypes.DefaultParams].
func (b *GenesisBuilder) DefaultMint() *GenesisBuilder {
return b.Mint(minttypes.DefaultInitialMinter(), minttypes.DefaultParams())
}
// Slashing sets the slashing genesis state.
func (b *GenesisBuilder) Slashing(
params slashingtypes.Params,
si []slashingtypes.SigningInfo,
mb []slashingtypes.ValidatorMissedBlocks,
) *GenesisBuilder {
var err error
b.appState[slashingtypes.ModuleName], err = b.codec.MarshalJSON(
slashingtypes.NewGenesisState(params, si, mb),
)
if err != nil {
panic(err)
}
return b
}
// SlashingWithDefaultParams calls b.Slashing with [slashingtypes.DefaultParams],
// so that callers don't necessarily have to import [slashingtypes].
func (b *GenesisBuilder) SlashingWithDefaultParams(
si []slashingtypes.SigningInfo,
mb []slashingtypes.ValidatorMissedBlocks,
) *GenesisBuilder {
return b.Slashing(slashingtypes.DefaultParams(), si, mb)
}
// DefaultSlashing is shorthand for b.SlashingWithDefaultParams
// with nil signing info and validator missed blocks.
func (b *GenesisBuilder) DefaultSlashing() *GenesisBuilder {
return b.SlashingWithDefaultParams(nil, nil)
}
// BaseAccounts sets the initial base accounts and balances.
func (b *GenesisBuilder) BaseAccounts(ba BaseAccounts, balances []banktypes.Balance) *GenesisBuilder {
// Logic mostly copied from AddGenesisAccount.
authGenState := authtypes.GetGenesisStateFromAppState(b.codec, b.appState)
bankGenState := banktypes.GetGenesisStateFromAppState(b.codec, b.appState)
accs, err := authtypes.UnpackAccounts(authGenState.Accounts)
if err != nil {
panic(err)
}
for _, a := range ba {
accs = append(accs, a)
}
accs = authtypes.SanitizeGenesisAccounts(accs)
genAccs, err := authtypes.PackAccounts(accs)
if err != nil {
panic(err)
}
authGenState.Accounts = genAccs
jAuthGenState, err := b.codec.MarshalJSON(&authGenState)
if err != nil {
panic(err)
}
b.appState[authtypes.ModuleName] = jAuthGenState
bankGenState.Balances = append(bankGenState.Balances, balances...)
bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances)
jBankState, err := b.codec.MarshalJSON(bankGenState)
if err != nil {
panic(err)
}
b.appState[banktypes.ModuleName] = jBankState
return b
}
func (b *GenesisBuilder) Distribution(g *distributiontypes.GenesisState) *GenesisBuilder {
j, err := b.codec.MarshalJSON(g)
if err != nil {
panic(err)
}
b.appState[distributiontypes.ModuleName] = j
return b
}
func (b *GenesisBuilder) DefaultDistribution() *GenesisBuilder {
return b.Distribution(distributiontypes.DefaultGenesisState())
}
// JSON returns the map of the genesis after applying some final transformations.
func (b *GenesisBuilder) JSON() map[string]json.RawMessage {
gentxGenesisState := genutiltypes.NewGenesisStateFromTx(
authtx.NewTxConfig(b.codec, authtx.DefaultSignModes).TxJSONEncoder(),
b.gentxs,
)
if err := genutiltypes.ValidateGenesis(
gentxGenesisState,
authtx.NewTxConfig(b.codec, authtx.DefaultSignModes).TxJSONDecoder(),
genutiltypes.DefaultMessageValidator,
); err != nil {
panic(err)
}
b.appState = genutiltypes.SetGenesisStateInAppState(
b.codec, b.appState, gentxGenesisState,
)
appState, err := b.amino.MarshalJSON(b.appState)
if err != nil {
panic(err)
}
b.outer["app_state"] = appState
return b.outer
}
// Encode returns the JSON-encoded, finalized genesis.
func (b *GenesisBuilder) Encode() []byte {
j, err := b.amino.MarshalJSON(b.JSON())
if err != nil {
panic(err)
}
return j
}
// DefaultGenesisBuilderOnlyValidators returns a GenesisBuilder configured only with the given StakingValidators,
// with default parameters everywhere else.
// validatorAmount is the amount to give each validator during gentx.
//
// This is a convenience for the common case of nothing special in the genesis.
// For anything outside of the defaults,
// the longhand form of NewGenesisBuilder().ChainID(chainID)... should be used.
func DefaultGenesisBuilderOnlyValidators(
chainID string,
sv StakingValidators,
validatorAmount sdk.Coin,
) *GenesisBuilder {
cmtVals := make(CometGenesisValidators, len(sv))
for i := range sv {
cmtVals[i] = sv[i].C
}
b := NewGenesisBuilder().
ChainID(chainID).
DefaultAuthParams().
Consensus(nil, cmtVals).
BaseAccounts(sv.BaseAccounts(), nil).
StakingWithDefaultParams(nil, nil).
BankingWithDefaultParams(sv.Balances(), nil, nil, nil).
DefaultDistribution().
DefaultMint().
SlashingWithDefaultParams(nil, nil)
for _, v := range sv {
b.GenTx(*v.PK.Del, v.C.V, sdk.NewCoin(sdk.DefaultBondDenom, sdk.DefaultPowerReduction))
}
return b
}

View File

@ -1,245 +0,0 @@
package testnet_test
import (
"encoding/base64"
"encoding/json"
"testing"
"time"
cmted25519 "github.com/cometbft/cometbft/crypto/ed25519"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/testutil/testnet"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
)
func TestGenesisBuilder_GenesisTime(t *testing.T) {
t.Run("defaults to current time", func(t *testing.T) {
before := time.Now()
time.Sleep(time.Millisecond) // So that the genesis time will be strictly after "before".
gb := testnet.NewGenesisBuilder()
time.Sleep(time.Millisecond) // So that the genesis time will be strictly before "after".
after := time.Now()
var gts string
require.NoError(t, json.Unmarshal(gb.JSON()["genesis_time"], &gts))
gt, err := time.Parse(time.RFC3339Nano, gts)
require.NoError(t, err)
require.True(t, gt.After(before))
require.True(t, after.After(gt))
})
t.Run("can be set to arbitrary time", func(t *testing.T) {
want := time.Date(2023, 3, 27, 9, 50, 23, 0, time.UTC)
gb := testnet.NewGenesisBuilder().GenesisTime(want)
var gts string
require.NoError(t, json.Unmarshal(gb.JSON()["genesis_time"], &gts))
gt, err := time.Parse(time.RFC3339Nano, gts)
require.NoError(t, err)
require.True(t, gt.Equal(want))
})
}
func TestGenesisBuilder_InitialHeight(t *testing.T) {
t.Run("defaults to 1", func(t *testing.T) {
var ih string
require.NoError(
t,
json.Unmarshal(
testnet.NewGenesisBuilder().JSON()["initial_height"],
&ih,
),
)
require.Equal(t, ih, "1")
})
t.Run("can be set to arbitrary value", func(t *testing.T) {
var ih string
require.NoError(
t,
json.Unmarshal(
testnet.NewGenesisBuilder().InitialHeight(12345).JSON()["initial_height"],
&ih,
),
)
require.Equal(t, ih, "12345")
})
}
func TestGenesisBuilder_ChainID(t *testing.T) {
// No default.
gb := testnet.NewGenesisBuilder()
m := gb.JSON()
_, ok := m["chain_id"]
require.False(t, ok)
var id string
require.NoError(
t,
json.Unmarshal(
gb.ChainID("my-chain").JSON()["chain_id"],
&id,
),
)
require.Equal(t, id, "my-chain")
}
// Use known keys and addresses to assert that correct validator and delegator keys
// occur in the expected locations (i.e. we didn't mistakenly swap the keys anywhere).
func TestGenesisBuilder_GentxAddresses(t *testing.T) {
const valSecret0 = "val-secret-0"
const valAddr0 = "3F3B076353767F046477A6E0982F808C24D1870A"
const valPubKey0 = "ZhVhrOUHnUwYw/GlBSBrw/0X6A261gchCRYkAxGF2jk="
valKey0 := cmted25519.GenPrivKeyFromSecret([]byte(valSecret0))
if addr := valKey0.PubKey().Address().String(); addr != valAddr0 {
t.Fatalf("unexpected address %q for validator key 0 (expected %q)", addr, valAddr0)
}
if pub := base64.StdEncoding.EncodeToString(valKey0.PubKey().Bytes()); pub != valPubKey0 {
t.Fatalf("unexpected public key %q for validator key 0 (expected %q)", pub, valAddr0)
}
const delSecret0 = "del-secret-0"
const delAddr0 = "30D7E04DA313C31B59A46408494B4272F0A9A256"
const delPubKey0 = "Aol+ZF9xBuZmYJrT1QFLpZBvSfr/zEKifWyg0Xi1tsFV"
const delAccAddr0 = "cosmos1xrt7qndrz0p3kkdyvsyyjj6zwtc2ngjky8dcpe"
delKey0 := secp256k1.GenPrivKeyFromSecret([]byte(delSecret0))
if addr := delKey0.PubKey().Address().String(); addr != delAddr0 {
t.Fatalf("unexpected address %q for delegator key 0 (expected %q)", addr, delAddr0)
}
if pub := base64.StdEncoding.EncodeToString(delKey0.PubKey().Bytes()); pub != delPubKey0 {
t.Fatalf("unexpected public key %q for delegator key 0 (expected %q)", pub, delAddr0)
}
da, err := bech32.ConvertAndEncode("cosmos", delKey0.PubKey().Address().Bytes())
require.NoError(t, err)
if da != delAccAddr0 {
t.Fatalf("unexpected account address %q for delegator key 0 (expected %q)", da, delAccAddr0)
}
valPKs := testnet.ValidatorPrivKeys{
&testnet.ValidatorPrivKey{
Val: valKey0,
Del: delKey0,
},
}
cmtVals := valPKs.CometGenesisValidators()
stakingVals := cmtVals.StakingValidators()
valBaseAccounts := stakingVals.BaseAccounts()
b := testnet.NewGenesisBuilder().
ChainID("my-test-chain").
DefaultAuthParams().
Consensus(nil, cmtVals).
BaseAccounts(valBaseAccounts, nil).
StakingWithDefaultParams(stakingVals, nil)
for i, v := range valPKs {
b.GenTx(*v.Del, cmtVals[i].V, sdk.NewCoin(sdk.DefaultBondDenom, sdk.DefaultPowerReduction))
}
var g struct {
Consensus struct {
Validators []struct {
Address string `json:"address"`
PubKey struct {
Value string `json:"value"`
} `json:"pub_key"`
} `json:"validators"`
} `json:"consensus"`
AppState struct {
Genutil struct {
GenTxs []struct {
Body struct {
Messages []struct {
Type string `json:"@type"`
DelegatorAddress string `json:"delegator_address"`
ValidatorAddress string `json:"validator_address"`
PubKey struct {
Key string `json:"key"`
} `json:"pubkey"`
} `json:"messages"`
} `json:"body"`
AuthInfo struct {
SignerInfos []struct {
PublicKey struct {
Key string `json:"key"`
} `json:"public_key"`
} `json:"signer_infos"`
} `json:"auth_info"`
} `json:"gen_txs"`
} `json:"genutil"`
Auth struct {
Accounts []struct {
Address string `json:"address"`
PubKey struct {
Key string `json:"key"`
} `json:"pub_key"`
} `json:"accounts"`
} `json:"auth"`
} `json:"app_state"`
}
if err := json.Unmarshal(b.Encode(), &g); err != nil {
t.Fatal(err)
}
// Validator encoded as expected.
vals := g.Consensus.Validators
require.Equal(t, vals[0].Address, valAddr0)
require.Equal(t, vals[0].PubKey.Value, valPubKey0)
// Public keys on gentx message match correct keys (no ed25519/secp256k1 mismatch).
gentxs := g.AppState.Genutil.GenTxs
require.Equal(t, gentxs[0].Body.Messages[0].PubKey.Key, valPubKey0)
require.Equal(t, gentxs[0].AuthInfo.SignerInfos[0].PublicKey.Key, delPubKey0)
// The only base account in this genesis, matches the secp256k1 key.
acct := g.AppState.Auth.Accounts[0]
require.Equal(t, acct.Address, delAccAddr0)
require.Equal(t, acct.PubKey.Key, delPubKey0)
}
func ExampleGenesisBuilder() {
const nVals = 4
// Generate random private keys for validators.
valPKs := testnet.NewValidatorPrivKeys(nVals)
// Produce the comet representation of those validators.
cmtVals := valPKs.CometGenesisValidators()
stakingVals := cmtVals.StakingValidators()
// Configure a new genesis builder
// with a fairly thorough set of defaults.
//
// If you only ever need defaults, you can use DefaultGenesisBuilderOnlyValidators().
b := testnet.NewGenesisBuilder().
ChainID("my-chain-id").
DefaultAuthParams().
Consensus(nil, cmtVals).
BaseAccounts(stakingVals.BaseAccounts(), nil).
DefaultStaking().
BankingWithDefaultParams(stakingVals.Balances(), nil, nil, nil).
DefaultDistribution().
DefaultMint().
DefaultSlashing()
for i := range stakingVals {
b.GenTx(*valPKs[i].Del, cmtVals[i].V, sdk.NewCoin(sdk.DefaultBondDenom, sdk.DefaultPowerReduction))
}
// Now, you can access b.JSON() if you need to make further modifications
// not (yet) supported by the GenesisBuilder API,
// or you can use b.Encode() for the serialzed JSON of the genesis.
}

View File

@ -1,116 +0,0 @@
package testnet
import (
"errors"
"fmt"
"sync"
"github.com/cometbft/cometbft/p2p"
)
// NewNetwork concurrently calls createCometStarter, nVals times;
// then it returns a slice of started comet nodes,
// in order corresponding with the number passed to createCometStarter.
// The returned nodes will all be peered together,
// by dialing each node's [github.com/cometbft/cometbft/p2p/pex.Reactor] to each other.
//
// Every node is attempted to be started,
// and any errors collected are joined together and returned.
//
// In the event of errors, a non-nil Nodes slice may still be returned
// and some elements may be nil.
// Callers should call [Nodes.Stop] and [Nodes.Wait] to perform cleanup,
// regardless of the returned error.
func NewNetwork(nVals int, createCometStarter func(int) *CometStarter) (Nodes, error) {
// The ordered slice of nodes; correct indexing is important.
// The creator goroutines will write directly into this slice.
nodes := make(Nodes, nVals)
// Every node will be started in its own goroutine.
// We collect the switches so that each node can dial every other node.
switchCh := make(chan *p2p.Switch, nVals)
errCh := make(chan error, nVals)
var wg sync.WaitGroup
// Start goroutines to populate nodes slice and notify as each node is available.
for i := 0; i < nVals; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
n, err := createCometStarter(i).Start()
if err != nil {
errCh <- fmt.Errorf("failed to start node %d: %w", i, err)
return
}
// Notify that the new node's switch is available, so this node can be
// peered with the other nodes.
switchCh <- n.Switch()
// And assign the node into its correct index in the ordered slice.
nodes[i] = n
}(i)
}
// Once all the creation goroutines are complete, close the channels,
// to signal to the collection goroutines.
go func() {
wg.Wait()
close(errCh)
close(switchCh)
}()
joinPeersDone := make(chan struct{})
go joinPeers(switchCh, joinPeersDone)
finalErrCh := make(chan error, 1)
go collectErrors(errCh, finalErrCh)
// If there were any errors, return them.
// And return any set nodes, so that they can be cleaned up properly.
if err := <-finalErrCh; err != nil {
return nodes, err
}
// No errors, so wait for peer joining to complete
// before returning the ordered nodes.
<-joinPeersDone
return nodes, nil
}
// collectErrors collects all errors that arrive on the in channel,
// joins them, then sends the joined final error on the out channel.
func collectErrors(in <-chan error, out chan<- error) {
var errs []error
for err := range in {
errs = append(errs, err)
}
var res error
if len(errs) > 0 {
res = errors.Join(errs...)
}
out <- res
}
// joinPeers collects each switch arriving on newSwitches;
// each time a new switch arrives, it dials every previously seen switch.
//
// This allows each node to be started independently and concurrently
// without predetermined p2p ports.
func joinPeers(newSwitches <-chan *p2p.Switch, done chan<- struct{}) {
defer close(done)
var readySwitches []*p2p.Switch
for newSwitch := range newSwitches {
newNetAddr := newSwitch.NetAddress()
for _, s := range readySwitches {
// For every new switch, connect with all the previously seen switches.
// It might not be necessary to dial in both directions, but it shouldn't hurt.
_ = s.DialPeerWithAddress(newNetAddr)
_ = newSwitch.DialPeerWithAddress(s.NetAddress())
}
readySwitches = append(readySwitches, newSwitch)
}
}

View File

@ -1,43 +0,0 @@
package testnet
import (
"fmt"
"time"
"github.com/cometbft/cometbft/node"
)
// WaitForNodeHeight blocks until the node's consensus state reports
// a last height equal to or greater than desiredHeight.
// If totalWait has elapsed and the desired height has not been reached,
// an error is returned.
func WaitForNodeHeight(n *node.Node, desiredHeight int64, totalWait time.Duration) error {
const backOff = 100 * time.Millisecond
attempts := int64(totalWait / backOff)
// In Comet 0.37, the consensus state was exposed directly on the Node.
// As of 0.38, the node no longer exposes consensus state,
// but the consensus state is available as a field on the RPC environment.
//
// Luckily, in 0.38 the RPC environment is no longer a package-level singleton,
// so retrieving the RPC environment for a single node should be safe.
env, err := n.ConfigureRPC()
if err != nil {
return fmt.Errorf("failed to configure RPC to reach into consensus state: %w", err)
}
curHeight := int64(-1)
for i := int64(0); i < attempts; i++ {
curHeight = env.ConsensusState.GetState().LastBlockHeight
if curHeight < desiredHeight {
time.Sleep(backOff)
continue
}
// Met or exceeded target height.
return nil
}
return fmt.Errorf("node did not reach desired height %d in %s; only reached height %d", desiredHeight, totalWait, curHeight)
}

View File

@ -1,70 +0,0 @@
package testnet
import (
"errors"
"fmt"
"time"
"github.com/cometbft/cometbft/node"
)
// Nodes is a slice of comet nodes,
// with some additional convenience methods.
//
// Nodes may contain nil elements,
// so that a partially failed call to NewNetwork
// can still be properly cleaned up.
type Nodes []*node.Node
// Stop stops each node sequentially.
// All errors occurring during stop are returned as a joined error.
//
// Nil elements in ns are skipped.
func (ns Nodes) Stop() error {
var errs []error
for i, n := range ns {
if n == nil {
continue
}
if err := n.Stop(); err != nil {
errs = append(errs, fmt.Errorf("failed to stop node %d: %w", i, err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
// Wait blocks until every node in ns has completely stopped.
//
// Nil elements in ns are skipped.
func (ns Nodes) Wait() {
for _, n := range ns {
if n == nil {
continue
}
n.Wait()
}
}
// StopAndWait is shorthand for calling both Stop() and Wait(),
// useful as a deferred call in tests.
func (ns Nodes) StopAndWait() error {
err := ns.Stop()
ns.Wait()
// TODO(mr): remove this sleep call after we are using a version of Comet
// that includes a fix for https://github.com/cometbft/cometbft/issues/646.
//
// On my machine, this sleep appears to completely eliminate the late file write.
// It also almost always works around https://github.com/cometbft/cometbft/issues/652.
//
// Presumably the fix for those two issues will be included in a v0.37.1 release.
// If not, I assume they will be part of the first v0.38 series release.
time.Sleep(250 * time.Millisecond)
return err
}

View File

@ -1,30 +0,0 @@
package testnet_test
import (
"testing"
"github.com/cometbft/cometbft/node"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/testutil/testnet"
)
// Nil entries in a Nodes slice don't fail Stop or Wait.
func TestNodes_StopWaitNil(t *testing.T) {
for _, tc := range []struct {
Name string
Nodes []*node.Node
}{
{Name: "nil slice", Nodes: nil},
{Name: "slice with nil elements", Nodes: []*node.Node{nil}},
} {
ns := testnet.Nodes(tc.Nodes)
t.Run(tc.Name, func(t *testing.T) {
require.NoError(t, ns.Stop())
// Nothing special to assert about Wait().
// It should return immediately, without panicking.
ns.Wait()
})
}
}

View File

@ -1,189 +0,0 @@
package testnet
import (
"fmt"
cmted25519 "github.com/cometbft/cometbft/crypto/ed25519"
cmttypes "github.com/cometbft/cometbft/types"
sdkmath "cosmossdk.io/math"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
// ValidatorPrivKeys is a slice of [*ValidatorPrivKey].
type ValidatorPrivKeys []*ValidatorPrivKey
// ValidatorPrivKey holds a validator key (a comet ed25519 key)
// and the validator's delegator or account key (a Cosmos SDK secp256k1 key).
type ValidatorPrivKey struct {
Val cmted25519.PrivKey
Del *secp256k1.PrivKey
}
// NewValidatorPrivKeys returns a ValidatorPrivKeys of length n,
// where each set of keys is dynamically generated.
//
// If writing a test where deterministic keys are required,
// the caller should manually construct a slice and assign each key as needed.
func NewValidatorPrivKeys(n int) ValidatorPrivKeys {
vpk := make(ValidatorPrivKeys, n)
for i := range vpk {
vpk[i] = &ValidatorPrivKey{
Val: cmted25519.GenPrivKey(),
Del: secp256k1.GenPrivKey(),
}
}
return vpk
}
// CometGenesisValidators derives the CometGenesisValidators belonging to vpk.
func (vpk ValidatorPrivKeys) CometGenesisValidators() CometGenesisValidators {
cgv := make(CometGenesisValidators, len(vpk))
for i, pk := range vpk {
pubKey := pk.Val.PubKey()
const votingPower = 1
cmtVal := cmttypes.NewValidator(pubKey, votingPower)
cgv[i] = &CometGenesisValidator{
V: cmttypes.GenesisValidator{
Address: cmtVal.Address,
PubKey: cmtVal.PubKey,
Power: cmtVal.VotingPower,
Name: fmt.Sprintf("val-%d", i),
},
PK: pk,
}
}
return cgv
}
// CometGenesisValidators is a slice of [*CometGenesisValidator].
type CometGenesisValidators []*CometGenesisValidator
// CometGenesisValidator holds a comet GenesisValidator
// and a reference to the ValidatorPrivKey from which the CometGenesisValidator was derived.
type CometGenesisValidator struct {
V cmttypes.GenesisValidator
PK *ValidatorPrivKey
}
// ToComet returns a new slice of [cmttypes.GenesisValidator],
// useful for some interactions.
func (cgv CometGenesisValidators) ToComet() []cmttypes.GenesisValidator {
vs := make([]cmttypes.GenesisValidator, len(cgv))
for i, v := range cgv {
vs[i] = v.V
}
return vs
}
// StakingValidators derives the StakingValidators belonging to cgv.
func (cgv CometGenesisValidators) StakingValidators() StakingValidators {
vals := make(StakingValidators, len(cgv))
for i, v := range cgv {
pk, err := cryptocodec.FromCmtPubKeyInterface(v.V.PubKey)
if err != nil {
panic(fmt.Errorf("failed to extract comet pub key: %w", err))
}
pkAny, err := codectypes.NewAnyWithValue(pk)
if err != nil {
panic(fmt.Errorf("failed to wrap pub key in any type: %w", err))
}
vals[i] = &StakingValidator{
V: stakingtypes.Validator{
OperatorAddress: sdk.ValAddress(v.V.Address).String(), // TODO: this relies on global bech32 config.
ConsensusPubkey: pkAny,
Status: stakingtypes.Bonded,
Tokens: sdk.DefaultPowerReduction,
DelegatorShares: sdkmath.LegacyOneDec(),
MinSelfDelegation: sdkmath.ZeroInt(),
// more fields uncopied from testutil/sims/app_helpers.go:220
},
C: v,
PK: v.PK,
}
}
return vals
}
// StakingValidators is a slice of [*StakingValidator].
type StakingValidators []*StakingValidator
// StakingValidator holds a [stakingtypes.Validator],
// and the CometGenesisValidator and ValidatorPrivKey required to derive the StakingValidator.
type StakingValidator struct {
V stakingtypes.Validator
C *CometGenesisValidator
PK *ValidatorPrivKey
}
// ToStakingType returns a new slice of [stakingtypes.Validator],
// useful for some interactions.
func (sv StakingValidators) ToStakingType() []stakingtypes.Validator {
vs := make([]stakingtypes.Validator, len(sv))
for i, v := range sv {
vs[i] = v.V
}
return vs
}
// BaseAccounts returns the BaseAccounts for this set of StakingValidators.
// The base accounts are important for [*GenesisBuilder.BaseAccounts].
func (sv StakingValidators) BaseAccounts() BaseAccounts {
ba := make(BaseAccounts, len(sv))
for i, v := range sv {
const accountNumber = 0
const sequenceNumber = 0
pubKey := v.PK.Del.PubKey()
bech, err := bech32.ConvertAndEncode("cosmos", pubKey.Address().Bytes()) // TODO: this shouldn't be hardcoded to cosmos!
if err != nil {
panic(err)
}
accAddr, err := sdk.AccAddressFromBech32(bech)
if err != nil {
panic(err)
}
ba[i] = authtypes.NewBaseAccount(
accAddr, pubKey, accountNumber, sequenceNumber,
)
}
return ba
}
// Balances returns the balances held by this set of StakingValidators.
func (sv StakingValidators) Balances() []banktypes.Balance {
bals := make([]banktypes.Balance, len(sv))
for i, v := range sv {
addr, err := bech32.ConvertAndEncode("cosmos", v.PK.Del.PubKey().Address().Bytes()) // TODO: this shouldn't be hardcoded to cosmos!
if err != nil {
panic(err)
}
bals[i] = banktypes.Balance{
Address: addr,
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, v.V.Tokens)},
}
}
return bals
}