diff --git a/go.mod b/go.mod index 91a2cb0eb5..42b518927a 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/celestiaorg/knuu v0.15.2 github.com/celestiaorg/nmt v0.22.1 github.com/celestiaorg/rsmt2d v0.14.0 + github.com/cometbft/cometbft-db v0.7.0 github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/cosmos-sdk v0.46.16 github.com/cosmos/gogoproto v1.7.0 @@ -59,17 +60,17 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/celestiaorg/bittwister v0.0.0-20231213180407-65cdbaf5b8c7 // indirect github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cilium/ebpf v0.12.3 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect - github.com/cometbft/cometbft-db v0.7.0 // indirect github.com/confio/ics23/go v0.9.1 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/containerd/continuity v0.4.2 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect @@ -85,6 +86,10 @@ require ( github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v26.1.5+incompatible // indirect + github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect @@ -171,6 +176,8 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/onsi/ginkgo v1.16.5 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -203,7 +210,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect - go.etcd.io/bbolt v1.3.6 // indirect + go.etcd.io/bbolt v1.3.10 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect diff --git a/go.sum b/go.sum index 78dc3649d2..d196f87450 100644 --- a/go.sum +++ b/go.sum @@ -213,6 +213,7 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/DataDog/zstd v1.5.0 h1:+K/VEwIAaPcHiMtQvpLD4lqW7f0Gk3xdYZmI1hD+CXo= github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -336,8 +337,8 @@ github.com/celestiaorg/rsmt2d v0.14.0/go.mod h1:4kxqiTdFev49sGiKXTDjohbWYOG5GlcI github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -403,8 +404,10 @@ github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1 github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= -github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= -github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -477,10 +480,13 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11 h1:IPrmumsT9t5BS7XcPhgsCTlkWbYg80SEXUzDpReaU6Y= github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11/go.mod h1:a6bNUGTbQBsY6VRHTr4h/rkOXjl244DyRD0tx3fgq4Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -1085,8 +1091,8 @@ github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeR github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= @@ -1209,6 +1215,7 @@ github.com/shirou/gopsutil v3.21.6+incompatible h1:mmZtAlWSd8U2HeRTjswbnDLPxqsEo github.com/shirou/gopsutil v3.21.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -1348,8 +1355,8 @@ gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975/go.mod h1:ZkMZ gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40 h1:dizWJqTWjwyD8KGcMOwgrkqu1JIkofYgKkmDeNE7oAs= gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40/go.mod h1:rOnSnoRyxMI3fe/7KIbVcsHRGxe30OONv8dEgo+vCfA= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= +go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -1629,7 +1636,6 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/user/account.go b/pkg/user/account.go index 8ab1d031f4..9d66dc0540 100644 --- a/pkg/user/account.go +++ b/pkg/user/account.go @@ -40,6 +40,10 @@ func (a Account) PubKey() cryptotypes.PubKey { return a.pubKey } +func (a Account) AccountNumber() uint64 { + return a.accountNumber +} + // Sequence returns the sequence number of the account. // This is locally tracked func (a Account) Sequence() uint64 { diff --git a/test/util/genesis/document.go b/test/util/genesis/document.go index 3cc61f08b9..f45921431b 100644 --- a/test/util/genesis/document.go +++ b/test/util/genesis/document.go @@ -24,6 +24,7 @@ func Document( chainID string, gentxs []json.RawMessage, accounts []Account, + genesisTime time.Time, mods ...Modifier, ) (*coretypes.GenesisDoc, error) { genutilGenState := genutiltypes.DefaultGenesisState() @@ -73,7 +74,7 @@ func Document( // Create the genesis doc genesisDoc := &coretypes.GenesisDoc{ ChainID: chainID, - GenesisTime: time.Now(), + GenesisTime: genesisTime, ConsensusParams: params, AppState: stateBz, } @@ -101,7 +102,6 @@ func accountsToSDKTypes(accounts []Account) ([]banktypes.Balance, []authtypes.Ge ) genBals[i] = banktypes.Balance{Address: addr.String(), Coins: balances.Sort()} - genAccs[i] = authtypes.NewBaseAccount(addr, account.PubKey, uint64(i), 0) } return genBals, genAccs, nil diff --git a/test/util/genesis/files.go b/test/util/genesis/files.go index ff702d3c15..6d2452187b 100644 --- a/test/util/genesis/files.go +++ b/test/util/genesis/files.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" "github.com/tendermint/tendermint/config" tmos "github.com/tendermint/tendermint/libs/os" "github.com/tendermint/tendermint/p2p" @@ -17,53 +18,58 @@ import ( func InitFiles( rootDir string, tmConfig *config.Config, + appCfg *srvconfig.Config, genesis *Genesis, validatorIndex int, -) (basePath string, err error) { +) error { val, has := genesis.Validator(validatorIndex) if !has { - return "", fmt.Errorf("validator %d not found", validatorIndex) + return fmt.Errorf("validator %d not found", validatorIndex) } - basePath = filepath.Join(rootDir, ".celestia-app") - tmConfig.SetRoot(basePath) + tmConfig.SetRoot(rootDir) // save the genesis file - configPath := filepath.Join(basePath, "config") - err = os.MkdirAll(configPath, os.ModePerm) + configPath := filepath.Join(rootDir, "config") + err := os.MkdirAll(configPath, os.ModePerm) if err != nil { - return "", err + return err } genesisDoc, err := genesis.Export() if err != nil { - return "", fmt.Errorf("exporting genesis: %w", err) + return fmt.Errorf("exporting genesis: %w", err) } err = genesisDoc.SaveAs(tmConfig.GenesisFile()) if err != nil { - return "", err + return err } pvStateFile := tmConfig.PrivValidatorStateFile() if err := tmos.EnsureDir(filepath.Dir(pvStateFile), 0o777); err != nil { - return "", err + return err } pvKeyFile := tmConfig.PrivValidatorKeyFile() if err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0o777); err != nil { - return "", err + return err } filePV := privval.NewFilePV(val.ConsensusKey, pvKeyFile, pvStateFile) filePV.Save() nodeKeyFile := tmConfig.NodeKeyFile() if err := tmos.EnsureDir(filepath.Dir(nodeKeyFile), 0o777); err != nil { - return "", err + return err } nodeKey := &p2p.NodeKey{ PrivKey: val.NetworkKey, } if err := nodeKey.SaveAs(nodeKeyFile); err != nil { - return "", err + return err } - return basePath, nil + appConfigFilePath := filepath.Join(rootDir, "config", "app.toml") + srvconfig.WriteConfigFile(appConfigFilePath, appCfg) + + config.WriteConfigFile(filepath.Join(rootDir, "config", "config.toml"), tmConfig) + + return nil } diff --git a/test/util/genesis/genesis.go b/test/util/genesis/genesis.go index 5a52fdbb29..83eab54250 100644 --- a/test/util/genesis/genesis.go +++ b/test/util/genesis/genesis.go @@ -120,6 +120,11 @@ func (g *Genesis) WithKeyringAccounts(accs ...KeyringAccount) *Genesis { return g } +func (g *Genesis) WithKeyring(kr keyring.Keyring) *Genesis { + g.kr = kr + return g +} + // AddAccount adds an existing account to the genesis. func (g *Genesis) AddAccount(account Account) error { if err := account.ValidateBasic(); err != nil { @@ -208,6 +213,7 @@ func (g *Genesis) Export() (*coretypes.GenesisDoc, error) { g.ChainID, gentxs, g.accounts, + g.GenesisTime, g.genOps..., ) } @@ -220,3 +226,7 @@ func (g *Genesis) Validator(i int) (Validator, bool) { } return Validator{}, false } + +func (g *Genesis) EncodingConfig() encoding.Config { + return g.ecfg +} diff --git a/test/util/testnode/network.go b/test/util/testnode/network.go index 79a1dc7d69..4c0421c895 100644 --- a/test/util/testnode/network.go +++ b/test/util/testnode/network.go @@ -2,6 +2,7 @@ package testnode import ( "context" + "path/filepath" "testing" "github.com/celestiaorg/celestia-app/v3/test/util/genesis" @@ -19,7 +20,8 @@ func NewNetwork(t testing.TB, config *Config) (cctx Context, rpcAddr, grpcAddr s t.Helper() // initialize the genesis file and validator files for the first validator. - baseDir, err := genesis.InitFiles(t.TempDir(), config.TmConfig, config.Genesis, 0) + baseDir := filepath.Join(t.TempDir(), "testnode") + err := genesis.InitFiles(baseDir, config.TmConfig, config.AppConfig, config.Genesis, 0) require.NoError(t, err) tmNode, app, err := NewCometNode(baseDir, &config.UniversalTestingConfig) diff --git a/tools/chainbuilder/README.md b/tools/chainbuilder/README.md new file mode 100644 index 0000000000..99911468ac --- /dev/null +++ b/tools/chainbuilder/README.md @@ -0,0 +1,27 @@ +# Chainbuilder + +`chainbuilder` is a tool for building a Celestia chain for testing and development purposes. + +## Usage + +Use `go` to run the binary as follows: + +``` +go run ./tools/chainbuilder +``` + +This will create a directory with the name `testnode-{chainID}`. All files will be populated and blocks generated based on specified input. You can run a validator on the file system afterwards by calling: + +``` +celestia-appd start --home /path/to/testnode-{chainID} +``` + +The following are the set of options when generating a chain: + +- `num-blocks` the number of blocks to be generated (default: 100) +- `block-size` the size of the blocks to be generated (default <2MB). This will be a single PFB transaction +- `square-size` the size of the max square (default: 128) +- `existing-dir` point this to a directory if you want to extend an existing chain rather than create a new one +- `namespace` allows you to pick a custom v0 namespace. By default "test" will be chosen. + +This tool takes roughly 60-70ms per 2MB block. diff --git a/tools/chainbuilder/benchmark_test.go b/tools/chainbuilder/benchmark_test.go new file mode 100644 index 0000000000..b72d9297e5 --- /dev/null +++ b/tools/chainbuilder/benchmark_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/celestiaorg/celestia-app/v3/pkg/appconsts" +) + +func BenchmarkRun(b *testing.B) { + cfg := BuilderConfig{ + NumBlocks: 100, + BlockSize: appconsts.DefaultMaxBytes, + BlockInterval: time.Second, + } + + dir := b.TempDir() + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := Run(context.Background(), cfg, dir); err != nil { + b.Fatal(err) + } + } +} diff --git a/tools/chainbuilder/integration_test.go b/tools/chainbuilder/integration_test.go new file mode 100644 index 0000000000..eb671cd1f0 --- /dev/null +++ b/tools/chainbuilder/integration_test.go @@ -0,0 +1,103 @@ +package main + +import ( + "context" + "fmt" + "path/filepath" + "testing" + "time" + + "github.com/celestiaorg/celestia-app/v3/app" + "github.com/celestiaorg/celestia-app/v3/app/encoding" + "github.com/celestiaorg/celestia-app/v3/pkg/appconsts" + "github.com/celestiaorg/celestia-app/v3/test/util" + "github.com/celestiaorg/celestia-app/v3/test/util/testnode" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/tendermint/tendermint/libs/log" + tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/rpc/client/local" + tmdbm "github.com/tendermint/tm-db" + + "github.com/stretchr/testify/require" +) + +func TestRun(t *testing.T) { + if testing.Short() { + t.Skip("skipping chainbuilder tool test") + } + + numBlocks := 10 + + cfg := BuilderConfig{ + NumBlocks: numBlocks, + BlockSize: appconsts.DefaultMaxBytes, + BlockInterval: time.Second, + ChainID: tmrand.Str(6), + Namespace: defaultNamespace, + } + + dir := t.TempDir() + + // First run + err := Run(context.Background(), cfg, dir) + require.NoError(t, err) + + // Second run with existing directory + cfg.ExistingDir = filepath.Join(dir, fmt.Sprintf("testnode-%s", cfg.ChainID)) + err = Run(context.Background(), cfg, dir) + require.NoError(t, err) + + tmCfg := testnode.DefaultTendermintConfig() + tmCfg.SetRoot(cfg.ExistingDir) + + appDB, err := tmdbm.NewDB("application", tmdbm.GoLevelDBBackend, tmCfg.DBDir()) + require.NoError(t, err) + + encCfg := encoding.MakeConfig(app.ModuleBasics) + + app := app.New( + log.NewNopLogger(), + appDB, + nil, + 0, + encCfg, + 0, + util.EmptyAppOptions{}, + baseapp.SetMinGasPrices(fmt.Sprintf("%f%s", appconsts.DefaultMinGasPrice, appconsts.BondDenom)), + ) + + nodeKey, err := p2p.LoadNodeKey(tmCfg.NodeKeyFile()) + require.NoError(t, err) + + cometNode, err := node.NewNode( + tmCfg, + privval.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()), + nodeKey, + proxy.NewLocalClientCreator(app), + node.DefaultGenesisDocProviderFunc(tmCfg), + node.DefaultDBProvider, + node.DefaultMetricsProvider(tmCfg.Instrumentation), + log.NewNopLogger(), + ) + require.NoError(t, err) + + require.NoError(t, cometNode.Start()) + defer func() { _ = cometNode.Stop() }() + + client := local.New(cometNode) + status, err := client.Status(context.Background()) + require.NoError(t, err) + require.NotNil(t, status) + // assert that the new node eventually makes progress in the chain + require.Eventually(t, func() bool { + status, err := client.Status(context.Background()) + require.NoError(t, err) + return status.SyncInfo.LatestBlockHeight >= int64(numBlocks*2) + }, time.Second*10, time.Millisecond*100) + require.NoError(t, cometNode.Stop()) + cometNode.Wait() +} diff --git a/tools/chainbuilder/main.go b/tools/chainbuilder/main.go new file mode 100644 index 0000000000..fe385084b3 --- /dev/null +++ b/tools/chainbuilder/main.go @@ -0,0 +1,547 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/celestiaorg/go-square/v2" + "github.com/celestiaorg/go-square/v2/share" + dbm "github.com/cometbft/cometbft-db" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/libs/log" + tmrand "github.com/tendermint/tendermint/libs/rand" + "github.com/tendermint/tendermint/privval" + smproto "github.com/tendermint/tendermint/proto/tendermint/state" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/store" + "github.com/tendermint/tendermint/types" + tmdbm "github.com/tendermint/tm-db" + + "github.com/celestiaorg/celestia-app/v3/app" + "github.com/celestiaorg/celestia-app/v3/app/encoding" + "github.com/celestiaorg/celestia-app/v3/pkg/appconsts" + "github.com/celestiaorg/celestia-app/v3/pkg/da" + "github.com/celestiaorg/celestia-app/v3/pkg/user" + "github.com/celestiaorg/celestia-app/v3/test/util" + "github.com/celestiaorg/celestia-app/v3/test/util/genesis" + "github.com/celestiaorg/celestia-app/v3/test/util/testnode" + blobtypes "github.com/celestiaorg/celestia-app/v3/x/blob/types" +) + +var defaultNamespace share.Namespace + +const ( + defaultNamespaceStr = "test" + maxSquareSize = 512 +) + +func init() { + defaultNamespace = share.MustNewV0Namespace([]byte(defaultNamespaceStr)) +} + +func main() { + rootCmd := &cobra.Command{ + Use: "chainbuilder", + Short: "Build a Celestia chain", + RunE: func(cmd *cobra.Command, _ []string) error { + numBlocks, _ := cmd.Flags().GetInt("num-blocks") + blockSize, _ := cmd.Flags().GetInt("block-size") + blockInterval, _ := cmd.Flags().GetDuration("block-interval") + existingDir, _ := cmd.Flags().GetString("existing-dir") + namespaceStr, _ := cmd.Flags().GetString("namespace") + upToTime, _ := cmd.Flags().GetBool("up-to-now") + appVersion, _ := cmd.Flags().GetUint64("app-version") + chainID, _ := cmd.Flags().GetString("chain-id") + var namespace share.Namespace + if namespaceStr == "" { + namespace = defaultNamespace + } else { + var err error + namespace, err = share.NewV0Namespace([]byte(namespaceStr)) + if err != nil { + return fmt.Errorf("invalid namespace: %w", err) + } + } + + cfg := BuilderConfig{ + NumBlocks: numBlocks, + BlockSize: blockSize, + BlockInterval: blockInterval, + ExistingDir: existingDir, + Namespace: namespace, + ChainID: tmrand.Str(6), + UpToTime: upToTime, + AppVersion: appVersion, + } + + if chainID != "" { + cfg.ChainID = chainID + } + + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get current working directory: %w", err) + } + + return Run(cmd.Context(), cfg, dir) + }, + } + + rootCmd.Flags().Int("num-blocks", 100, "Number of blocks to generate") + rootCmd.Flags().Int("block-size", appconsts.DefaultMaxBytes, "Size of each block in bytes") + rootCmd.Flags().Duration("block-interval", time.Second, "Interval between blocks") + rootCmd.Flags().String("existing-dir", "", "Existing directory to load chain from") + rootCmd.Flags().String("namespace", "", "Custom namespace for the chain") + rootCmd.Flags().Bool("up-to-now", false, "Tool will terminate if the block time reaches the current time") + rootCmd.Flags().Uint64("app-version", appconsts.LatestVersion, "App version to use for the chain") + rootCmd.Flags().String("chain-id", "", "Chain ID to use for the chain. Defaults to a random 6 character string") + rootCmd.SilenceUsage = true + rootCmd.SilenceErrors = true + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +type BuilderConfig struct { + NumBlocks int + BlockSize int + BlockInterval time.Duration + ExistingDir string + Namespace share.Namespace + ChainID string + AppVersion uint64 + UpToTime bool +} + +func Run(ctx context.Context, cfg BuilderConfig, dir string) error { + startTime := time.Now().Add(-1 * cfg.BlockInterval * time.Duration(cfg.NumBlocks)).UTC() + currentTime := startTime + + encCfg := encoding.MakeConfig(app.ModuleBasics) + tmCfg := app.DefaultConsensusConfig() + var ( + gen *genesis.Genesis + kr keyring.Keyring + err error + ) + if cfg.ExistingDir == "" { + dir = filepath.Join(dir, fmt.Sprintf("testnode-%s", cfg.ChainID)) + kr, err = keyring.New(app.Name, keyring.BackendTest, dir, nil, encCfg.Codec) + if err != nil { + return fmt.Errorf("failed to create keyring: %w", err) + } + + validator := genesis.NewDefaultValidator(testnode.DefaultValidatorAccountName) + appCfg := app.DefaultAppConfig() + appCfg.Pruning = "everything" // we just want the last two states + appCfg.StateSync.SnapshotInterval = 0 + cp := app.DefaultConsensusParams() + + cp.Version.AppVersion = cfg.AppVersion // set the app version + gen = genesis.NewDefaultGenesis(). + WithConsensusParams(cp). + WithKeyring(kr). + WithChainID(cfg.ChainID). + WithGenesisTime(startTime). + WithValidators(validator) + + if err := genesis.InitFiles(dir, tmCfg, appCfg, gen, 0); err != nil { + return fmt.Errorf("failed to initialize genesis files: %w", err) + } + fmt.Println("Creating chain from scratch with Chain ID:", gen.ChainID) + } else { + cfgPath := filepath.Join(cfg.ExistingDir, "config/config.toml") + if _, err := os.Stat(cfgPath); os.IsNotExist(err) { + return fmt.Errorf("config file for existing chain not found at %s", cfgPath) + } + fmt.Println("Loading chain from existing directory:", cfg.ExistingDir) + tmCfg.SetRoot(cfg.ExistingDir) + kr, err = keyring.New(app.Name, keyring.BackendTest, cfg.ExistingDir, nil, encCfg.Codec) + if err != nil { + return fmt.Errorf("failed to load keyring: %w", err) + } + } + + validatorKey := privval.LoadFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()) + validatorAddr := validatorKey.Key.Address + + blockDB, err := dbm.NewDB("blockstore", dbm.GoLevelDBBackend, tmCfg.DBDir()) + if err != nil { + return fmt.Errorf("failed to create block database: %w", err) + } + + blockStore := store.NewBlockStore(blockDB) + + stateDB, err := dbm.NewDB("state", dbm.GoLevelDBBackend, tmCfg.DBDir()) + if err != nil { + return fmt.Errorf("failed to create state database: %w", err) + } + + stateStore := sm.NewStore(stateDB, sm.StoreOptions{ + DiscardABCIResponses: true, + }) + + appDB, err := tmdbm.NewDB("application", tmdbm.GoLevelDBBackend, tmCfg.DBDir()) + if err != nil { + return fmt.Errorf("failed to create application database: %w", err) + } + + simApp := app.New( + log.NewNopLogger(), + appDB, + nil, + 0, + encCfg, + 0, + util.EmptyAppOptions{}, + baseapp.SetMinGasPrices(fmt.Sprintf("%f%s", appconsts.DefaultMinGasPrice, appconsts.BondDenom)), + ) + + infoResp := simApp.Info(abci.RequestInfo{}) + + lastHeight := blockStore.Height() + if infoResp.LastBlockHeight != lastHeight { + return fmt.Errorf("last application height is %d, but the block store height is %d", infoResp.LastBlockHeight, lastHeight) + } + + if lastHeight == 0 { + if gen == nil { + return fmt.Errorf("non empty directory but no blocks found") + } + + genDoc, err := gen.Export() + if err != nil { + return fmt.Errorf("failed to export genesis document: %w", err) + } + + state, err := stateStore.LoadFromDBOrGenesisDoc(genDoc) + if err != nil { + return fmt.Errorf("failed to load state from database or genesis document: %w", err) + } + + validators := make([]*types.Validator, len(genDoc.Validators)) + for i, val := range genDoc.Validators { + validators[i] = types.NewValidator(val.PubKey, val.Power) + } + validatorSet := types.NewValidatorSet(validators) + nextVals := types.TM2PB.ValidatorUpdates(validatorSet) + csParams := types.TM2PB.ConsensusParams(genDoc.ConsensusParams) + res := simApp.InitChain(abci.RequestInitChain{ + ChainId: genDoc.ChainID, + Time: genDoc.GenesisTime, + ConsensusParams: csParams, + Validators: nextVals, + AppStateBytes: genDoc.AppState, + InitialHeight: genDoc.InitialHeight, + }) + + vals, err := types.PB2TM.ValidatorUpdates(res.Validators) + if err != nil { + return fmt.Errorf("failed to convert validator updates: %w", err) + } + state.Validators = types.NewValidatorSet(vals) + state.NextValidators = types.NewValidatorSet(vals).CopyIncrementProposerPriority(1) + state.AppHash = res.AppHash + state.LastResultsHash = merkle.HashFromByteSlices(nil) + if err := stateStore.Save(state); err != nil { + return fmt.Errorf("failed to save initial state: %w", err) + } + currentTime = currentTime.Add(cfg.BlockInterval) + } else { + fmt.Println("Starting from height", lastHeight) + } + state, err := stateStore.Load() + if err != nil { + return fmt.Errorf("failed to load state: %w", err) + } + if cfg.ExistingDir != "" { + // if this is extending an existing chain, we want to start + // the time to be where the existing chain left off + currentTime = state.LastBlockTime.Add(cfg.BlockInterval) + } + + if state.ConsensusParams.Version.AppVersion != cfg.AppVersion { + return fmt.Errorf("app version mismatch: state has %d, but cfg has %d", state.ConsensusParams.Version.AppVersion, cfg.AppVersion) + } + + if state.LastBlockHeight != lastHeight { + return fmt.Errorf("last block height mismatch: state has %d, but block store has %d", state.LastBlockHeight, lastHeight) + } + + validatorPower := state.Validators.Validators[0].VotingPower + + signer, err := user.NewSigner( + kr, + encCfg.TxConfig, + state.ChainID, + state.ConsensusParams.Version.AppVersion, + user.NewAccount(testnode.DefaultValidatorAccountName, 0, uint64(lastHeight)+1), + ) + if err != nil { + return fmt.Errorf("failed to create new signer: %w", err) + } + + var ( + errCh = make(chan error, 2) + dataCh = make(chan *tmproto.Data, 100) + persistCh = make(chan persistData, 100) + commit = types.NewCommit(0, 0, types.BlockID{}, nil) + ) + if lastHeight > 0 { + commit = blockStore.LoadSeenCommit(lastHeight) + } + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + go func() { + errCh <- generateSquareRoutine(ctx, signer, cfg, dataCh) + }() + + go func() { + errCh <- persistDataRoutine(ctx, stateStore, blockStore, persistCh) + }() + + lastBlock := blockStore.LoadBlock(blockStore.Height()) + + for height := lastHeight + 1; height <= int64(cfg.NumBlocks)+lastHeight; height++ { + if cfg.UpToTime && lastBlock != nil && lastBlock.Time.Add(cfg.BlockInterval).After(time.Now().UTC()) { + fmt.Printf("blocks cannot be generated into the future, stopping at height %d\n", lastBlock.Height) + break + } + + select { + case <-ctx.Done(): + return ctx.Err() + case dataPB := <-dataCh: + data, err := types.DataFromProto(dataPB) + if err != nil { + return fmt.Errorf("failed to convert data from protobuf: %w", err) + } + block, blockParts := state.MakeBlock(height, data, commit, nil, validatorAddr) + blockID := types.BlockID{ + Hash: block.Hash(), + PartSetHeader: blockParts.Header(), + } + + precommitVote := &tmproto.Vote{ + Height: height, + Round: 0, + Type: tmproto.PrecommitType, + BlockID: blockID.ToProto(), + ValidatorAddress: validatorAddr, + Timestamp: currentTime, + Signature: nil, + } + + if err := validatorKey.SignVote(state.ChainID, precommitVote); err != nil { + return fmt.Errorf("failed to sign precommit vote (%s): %w", precommitVote.String(), err) + } + + commitSig := types.CommitSig{ + BlockIDFlag: types.BlockIDFlagCommit, + ValidatorAddress: validatorAddr, + Timestamp: currentTime, + Signature: precommitVote.Signature, + } + commit = types.NewCommit(height, 0, blockID, []types.CommitSig{commitSig}) + + var lastCommitInfo abci.LastCommitInfo + if height > 1 { + lastCommitInfo = abci.LastCommitInfo{ + Round: 0, + Votes: []abci.VoteInfo{ + { + Validator: abci.Validator{ + Address: validatorAddr, + Power: validatorPower, + }, + SignedLastBlock: true, + }, + }, + } + } + + beginBlockResp := simApp.BeginBlock(abci.RequestBeginBlock{ + Hash: block.Hash(), + Header: *block.Header.ToProto(), + LastCommitInfo: lastCommitInfo, + }) + + deliverTxResponses := make([]*abci.ResponseDeliverTx, len(block.Data.Txs)) + + for idx, tx := range block.Data.Txs { + blobTx, isBlobTx := types.UnmarshalBlobTx(tx) + if isBlobTx { + tx = blobTx.Tx + } + deliverTxResponse := simApp.DeliverTx(abci.RequestDeliverTx{ + Tx: tx, + }) + if deliverTxResponse.Code != abci.CodeTypeOK { + return fmt.Errorf("failed to deliver tx: %s", deliverTxResponse.Log) + } + deliverTxResponses[idx] = &deliverTxResponse + } + + endBlockResp := simApp.EndBlock(abci.RequestEndBlock{ + Height: block.Height, + }) + + commitResp := simApp.Commit() + state.LastBlockHeight = height + state.LastBlockID = blockID + state.LastBlockTime = block.Time + state.LastValidators = state.Validators + state.Validators = state.NextValidators + state.NextValidators = state.NextValidators.CopyIncrementProposerPriority(1) + state.AppHash = commitResp.Data + state.LastResultsHash = sm.ABCIResponsesResultsHash(&smproto.ABCIResponses{ + DeliverTxs: deliverTxResponses, + BeginBlock: &beginBlockResp, + EndBlock: &endBlockResp, + }) + currentTime = currentTime.Add(cfg.BlockInterval) + persistCh <- persistData{ + state: state.Copy(), + block: block, + seenCommit: &types.Commit{ + Height: commit.Height, + Round: commit.Round, + BlockID: commit.BlockID, + Signatures: []types.CommitSig{commitSig}, + }, + } + } + } + + close(dataCh) + close(persistCh) + + var firstErr error + for i := 0; i < cap(errCh); i++ { + err := <-errCh + if err != nil && firstErr == nil { + firstErr = err + } + } + + if err := blockDB.Close(); err != nil { + return fmt.Errorf("failed to close block database: %w", err) + } + if err := stateDB.Close(); err != nil { + return fmt.Errorf("failed to close state database: %w", err) + } + if err := appDB.Close(); err != nil { + return fmt.Errorf("failed to close application database: %w", err) + } + + fmt.Println("Chain built successfully", state.LastBlockHeight) + + return firstErr +} + +func generateSquareRoutine( + ctx context.Context, + signer *user.Signer, + cfg BuilderConfig, + dataCh chan<- *tmproto.Data, +) error { + for i := 0; i < cfg.NumBlocks; i++ { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + account := signer.Accounts()[0] + + blob, err := share.NewV0Blob(cfg.Namespace, crypto.CRandBytes(cfg.BlockSize)) + if err != nil { + return err + } + + blobGas := blobtypes.DefaultEstimateGas([]uint32{uint32(cfg.BlockSize)}) + fee := float64(blobGas) * appconsts.DefaultMinGasPrice * 2 + tx, _, err := signer.CreatePayForBlobs(account.Name(), []*share.Blob{blob}, user.SetGasLimit(blobGas), user.SetFee(uint64(fee))) + if err != nil { + return err + } + if err := signer.IncrementSequence(account.Name()); err != nil { + return err + } + + dataSquare, txs, err := square.Build( + [][]byte{tx}, + maxSquareSize, + appconsts.SubtreeRootThreshold(1), + ) + if err != nil { + return err + } + + eds, err := da.ExtendShares(share.ToBytes(dataSquare)) + if err != nil { + return err + } + + dah, err := da.NewDataAvailabilityHeader(eds) + if err != nil { + return err + } + + select { + case dataCh <- &tmproto.Data{ + Txs: txs, + Hash: dah.Hash(), + SquareSize: uint64(dataSquare.Size()), + }: + case <-ctx.Done(): + return ctx.Err() + } + } + return nil +} + +type persistData struct { + state sm.State + block *types.Block + seenCommit *types.Commit +} + +func persistDataRoutine( + ctx context.Context, + stateStore sm.Store, + blockStore *store.BlockStore, + dataCh <-chan persistData, +) error { + for { + select { + case <-ctx.Done(): + return ctx.Err() + case data, ok := <-dataCh: + if !ok { + return nil + } + blockParts := data.block.MakePartSet(types.BlockPartSizeBytes) + blockStore.SaveBlock(data.block, blockParts, data.seenCommit) + if blockStore.Height()%100 == 0 { + fmt.Println("Reached height", blockStore.Height()) + } + + if err := stateStore.Save(data.state); err != nil { + return err + } + } + } +}