diff --git a/Makefile b/Makefile index 47288e3c76..308ffe2740 100644 --- a/Makefile +++ b/Makefile @@ -145,7 +145,7 @@ test-race: # TODO: Remove the -skip flag once the following tests no longer contain data races. # https://github.com/celestiaorg/celestia-app/issues/1369 @echo "--> Running tests in race mode" - @go test ./... -v -race -skip "TestPrepareProposalConsistency|TestIntegrationTestSuite|TestBlobstreamRPCQueries|TestSquareSizeIntegrationTest|TestStandardSDKIntegrationTestSuite|TestTxsimCommandFlags|TestTxsimCommandEnvVar|TestMintIntegrationTestSuite|TestBlobstreamCLI|TestUpgrade|TestMaliciousTestNode|TestBigBlobSuite|TestQGBIntegrationSuite|TestSignerTestSuite|TestPriorityTestSuite|TestTimeInPrepareProposalContext|TestBlobstream|TestCLITestSuite|TestLegacyUpgrade|TestSignerTwins|TestConcurrentTxSubmission" + @go test ./... -v -race -skip "TestPrepareProposalConsistency|TestIntegrationTestSuite|TestBlobstreamRPCQueries|TestSquareSizeIntegrationTest|TestStandardSDKIntegrationTestSuite|TestTxsimCommandFlags|TestTxsimCommandEnvVar|TestMintIntegrationTestSuite|TestBlobstreamCLI|TestUpgrade|TestMaliciousTestNode|TestBigBlobSuite|TestQGBIntegrationSuite|TestSignerTestSuite|TestPriorityTestSuite|TestTimeInPrepareProposalContext|TestBlobstream|TestCLITestSuite|TestLegacyUpgrade|TestSignerTwins|TestConcurrentTxSubmission|TestTxClientTestSuite" .PHONY: test-race ## test-bench: Run unit tests in bench mode. diff --git a/app/errors/insufficient_gas_price_test.go b/app/errors/insufficient_gas_price_test.go index 2c6ef288c6..57b32c0c20 100644 --- a/app/errors/insufficient_gas_price_test.go +++ b/app/errors/insufficient_gas_price_test.go @@ -37,22 +37,25 @@ func TestInsufficientMinGasPriceIntegration(t *testing.T) { addr := testfactory.GetAddress(kr, account) enc := encoding.MakeConfig(app.ModuleEncodingRegisters...) acc := testutil.DirectQueryAccount(testApp, addr) - signer, err := user.NewSigner(kr, nil, addr, enc.TxConfig, testutil.ChainID, acc.GetAccountNumber(), acc.GetSequence(), appconsts.LatestVersion) + signer, err := user.NewSigner(kr, enc.TxConfig, testutil.ChainID, appconsts.LatestVersion, user.NewAccount(account, acc.GetAccountNumber(), acc.GetSequence())) require.NoError(t, err) fee := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(feeAmount))) b, err := blob.NewBlob(namespace.RandomNamespace(), []byte("hello world"), 0) require.NoError(t, err) - msg, err := blob.NewMsgPayForBlobs(signer.Address().String(), appconsts.LatestVersion, b) + msg, err := blob.NewMsgPayForBlobs(signer.Account(account).Address().String(), appconsts.LatestVersion, b) require.NoError(t, err) - sdkTx, err := signer.CreateTx([]sdk.Msg{msg}, user.SetGasLimit(gasLimit), user.SetFeeAmount(fee)) + rawTx, err := signer.CreateTx([]sdk.Msg{msg}, user.SetGasLimit(gasLimit), user.SetFeeAmount(fee)) require.NoError(t, err) decorator := ante.NewDeductFeeDecorator(testApp.AccountKeeper, testApp.BankKeeper, testApp.FeeGrantKeeper, nil) anteHandler := sdk.ChainAnteDecorators(decorator) + sdkTx, err := signer.DecodeTx(rawTx) + require.NoError(t, err) + _, err = anteHandler(ctx, sdkTx, false) require.True(t, apperr.IsInsufficientMinGasPrice(err)) actualGasPrice, err := apperr.ParseInsufficientMinGasPrice(err, gasPrice, gasLimit) diff --git a/app/errors/nonce_mismatch_test.go b/app/errors/nonce_mismatch_test.go index c95c86cd57..b07cb76147 100644 --- a/app/errors/nonce_mismatch_test.go +++ b/app/errors/nonce_mismatch_test.go @@ -32,21 +32,24 @@ func TestNonceMismatchIntegration(t *testing.T) { enc := encoding.MakeConfig(app.ModuleEncodingRegisters...) acc := testutil.DirectQueryAccount(testApp, addr) // set the sequence to an incorrect value - signer, err := user.NewSigner(kr, nil, addr, enc.TxConfig, testutil.ChainID, acc.GetAccountNumber(), 2, appconsts.LatestVersion) + signer, err := user.NewSigner(kr, enc.TxConfig, testutil.ChainID, appconsts.LatestVersion, user.NewAccount(account, acc.GetAccountNumber(), acc.GetSequence()+1)) require.NoError(t, err) b, err := blob.NewBlob(namespace.RandomNamespace(), []byte("hello world"), 0) require.NoError(t, err) - msg, err := blob.NewMsgPayForBlobs(signer.Address().String(), appconsts.LatestVersion, b) + msg, err := blob.NewMsgPayForBlobs(signer.Account(account).Address().String(), appconsts.LatestVersion, b) require.NoError(t, err) - sdkTx, err := signer.CreateTx([]sdk.Msg{msg}) + rawTx, err := signer.CreateTx([]sdk.Msg{msg}) require.NoError(t, err) decorator := ante.NewSigVerificationDecorator(testApp.AccountKeeper, encCfg.TxConfig.SignModeHandler()) anteHandler := sdk.ChainAnteDecorators(decorator) + sdkTx, err := signer.DecodeTx(rawTx) + require.NoError(t, err) + // We set simulate to true here to bypass having to initialize the // accounts public key. _, err = anteHandler(ctx, sdkTx, true) diff --git a/app/test/big_blob_test.go b/app/test/big_blob_test.go index 31fde2bf14..8e23e8f6c6 100644 --- a/app/test/big_blob_test.go +++ b/app/test/big_blob_test.go @@ -73,14 +73,14 @@ func (s *BigBlobSuite) TestErrBlobsTooLarge() { }, } - signer, err := testnode.NewSignerFromContext(s.cctx, s.accounts[0]) + txClient, err := testnode.NewTxClientFromContext(s.cctx) require.NoError(t, err) for _, tc := range testCases { s.Run(tc.name, func() { subCtx, cancel := context.WithTimeout(s.cctx.GoContext(), 30*time.Second) defer cancel() - res, err := signer.SubmitPayForBlob(subCtx, []*blob.Blob{tc.blob}, user.SetGasLimitAndFee(1e9, appconsts.DefaultMinGasPrice)) + res, err := txClient.SubmitPayForBlob(subCtx, []*blob.Blob{tc.blob}, user.SetGasLimitAndFee(1e9, appconsts.DefaultMinGasPrice)) require.Error(t, err) require.NotNil(t, res) require.Equal(t, tc.want, res.Code, res.Logs) diff --git a/app/test/check_tx_test.go b/app/test/check_tx_test.go index f625b99048..8502941300 100644 --- a/app/test/check_tx_test.go +++ b/app/test/check_tx_test.go @@ -14,7 +14,6 @@ import ( "github.com/celestiaorg/celestia-app/v2/pkg/user" testutil "github.com/celestiaorg/celestia-app/v2/test/util" "github.com/celestiaorg/celestia-app/v2/test/util/blobfactory" - "github.com/celestiaorg/celestia-app/v2/test/util/testfactory" blobtypes "github.com/celestiaorg/celestia-app/v2/x/blob/types" "github.com/celestiaorg/go-square/blob" appns "github.com/celestiaorg/go-square/namespace" @@ -112,8 +111,8 @@ func TestCheckTx(t *testing.T) { checkType: abci.CheckTxType_New, getTx: func() []byte { signer := createSigner(t, kr, accs[4], encCfg.TxConfig, 5) - _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Address().String(), 10_000, 10) - tx, err := signer.CreatePayForBlob(blobs, opts...) + _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Account(accs[4]).Address().String(), 10_000, 10) + tx, _, err := signer.CreatePayForBlobs(accs[4], blobs, opts...) require.NoError(t, err) return tx }, @@ -124,8 +123,8 @@ func TestCheckTx(t *testing.T) { checkType: abci.CheckTxType_New, getTx: func() []byte { signer := createSigner(t, kr, accs[5], encCfg.TxConfig, 6) - _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Address().String(), 1_000, 1) - tx, err := signer.CreatePayForBlob(blobs, opts...) + _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Account(accs[5]).Address().String(), 1_000, 1) + tx, _, err := signer.CreatePayForBlobs(accs[5], blobs, opts...) require.NoError(t, err) return tx }, @@ -136,8 +135,8 @@ func TestCheckTx(t *testing.T) { checkType: abci.CheckTxType_New, getTx: func() []byte { signer := createSigner(t, kr, accs[6], encCfg.TxConfig, 7) - _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Address().String(), 10_000, 1) - tx, err := signer.CreatePayForBlob(blobs, opts...) + _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Account(accs[6]).Address().String(), 10_000, 1) + tx, _, err := signer.CreatePayForBlobs(accs[6], blobs, opts...) require.NoError(t, err) return tx }, @@ -148,8 +147,8 @@ func TestCheckTx(t *testing.T) { checkType: abci.CheckTxType_New, getTx: func() []byte { signer := createSigner(t, kr, accs[7], encCfg.TxConfig, 8) - _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Address().String(), 100_000, 1) - tx, err := signer.CreatePayForBlob(blobs, opts...) + _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Account(accs[7]).Address().String(), 100_000, 1) + tx, _, err := signer.CreatePayForBlobs(accs[7], blobs, opts...) require.NoError(t, err) return tx }, @@ -160,8 +159,8 @@ func TestCheckTx(t *testing.T) { checkType: abci.CheckTxType_New, getTx: func() []byte { signer := createSigner(t, kr, accs[8], encCfg.TxConfig, 9) - _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Address().String(), 1_000_000, 1) - tx, err := signer.CreatePayForBlob(blobs, opts...) + _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Account(accs[8]).Address().String(), 1_000_000, 1) + tx, _, err := signer.CreatePayForBlobs(accs[8], blobs, opts...) require.NoError(t, err) return tx }, @@ -172,8 +171,8 @@ func TestCheckTx(t *testing.T) { checkType: abci.CheckTxType_New, getTx: func() []byte { signer := createSigner(t, kr, accs[9], encCfg.TxConfig, 10) - _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Address().String(), 10_000_000, 1) - tx, err := signer.CreatePayForBlob(blobs, opts...) + _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), signer.Account(accs[9]).Address().String(), 10_000_000, 1) + tx, _, err := signer.CreatePayForBlobs(accs[9], blobs, opts...) require.NoError(t, err) return tx }, @@ -190,8 +189,7 @@ func TestCheckTx(t *testing.T) { } func createSigner(t *testing.T, kr keyring.Keyring, accountName string, enc client.TxConfig, accNum uint64) *user.Signer { - addr := testfactory.GetAddress(kr, accountName) - signer, err := user.NewSigner(kr, nil, addr, enc, testutil.ChainID, accNum, 0, appconsts.LatestVersion) + signer, err := user.NewSigner(kr, enc, testutil.ChainID, appconsts.LatestVersion, user.NewAccount(accountName, accNum, 0)) require.NoError(t, err) return signer } diff --git a/app/test/integration_test.go b/app/test/integration_test.go index 7added9a40..274b4264cd 100644 --- a/app/test/integration_test.go +++ b/app/test/integration_test.go @@ -63,7 +63,7 @@ func (s *IntegrationTestSuite) SetupSuite() { for _, acc := range s.accounts { addr := testfactory.GetAddress(s.cctx.Keyring, acc) - _, _, err := user.QueryAccount(s.cctx.GoContext(), s.cctx.GRPCClient, s.ecfg, addr.String()) + _, _, err := user.QueryAccount(s.cctx.GoContext(), s.cctx.GRPCClient, s.cctx.InterfaceRegistry, addr) require.NoError(t, err) } } diff --git a/app/test/prepare_proposal_context_test.go b/app/test/prepare_proposal_context_test.go index 62aa1f836d..cb181a2a31 100644 --- a/app/test/prepare_proposal_context_test.go +++ b/app/test/prepare_proposal_context_test.go @@ -17,7 +17,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - tmrand "github.com/tendermint/tendermint/libs/rand" ) // TestTimeInPrepareProposalContext checks for an edge case where the block time @@ -29,18 +28,15 @@ func TestTimeInPrepareProposalContext(t *testing.T) { if testing.Short() { t.Skip("skipping TestTimeInPrepareProposalContext test in short mode.") } - accounts := make([]string, 35) - for i := 0; i < len(accounts); i++ { - accounts[i] = tmrand.Str(9) - } - cfg := testnode.DefaultConfig().WithFundedAccounts(accounts...) + sendAccName := "sending" + vestAccName := "vesting" + cfg := testnode.DefaultConfig().WithFundedAccounts(sendAccName) cctx, _, _ := testnode.NewNetwork(t, cfg) ecfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) - vestAccName := "vesting" + type test struct { - name string - msgFunc func() (msgs []sdk.Msg, signer string) - expectedCode uint32 + name string + msgFunc func() (msgs []sdk.Msg, signer string) } tests := []test{ { @@ -48,8 +44,7 @@ func TestTimeInPrepareProposalContext(t *testing.T) { msgFunc: func() (msgs []sdk.Msg, signer string) { _, _, err := cctx.Keyring.NewMnemonic(vestAccName, keyring.English, "", "", hd.Secp256k1) require.NoError(t, err) - sendAcc := accounts[0] - sendingAccAddr := testfactory.GetAddress(cctx.Keyring, sendAcc) + sendingAccAddr := testfactory.GetAddress(cctx.Keyring, sendAccName) vestAccAddr := testfactory.GetAddress(cctx.Keyring, vestAccName) msg := vestingtypes.NewMsgCreateVestingAccount( sendingAccAddr, @@ -59,15 +54,13 @@ func TestTimeInPrepareProposalContext(t *testing.T) { time.Now().Add(time.Second*100).Unix(), false, ) - return []sdk.Msg{msg}, sendAcc + return []sdk.Msg{msg}, sendAccName }, - expectedCode: abci.CodeTypeOK, }, { name: "send funds from the vesting account after it has been created", msgFunc: func() (msgs []sdk.Msg, signer string) { - sendAcc := accounts[1] - sendingAccAddr := testfactory.GetAddress(cctx.Keyring, sendAcc) + sendingAccAddr := testfactory.GetAddress(cctx.Keyring, sendAccName) vestAccAddr := testfactory.GetAddress(cctx.Keyring, vestAccName) msg := banktypes.NewMsgSend( vestAccAddr, @@ -76,25 +69,19 @@ func TestTimeInPrepareProposalContext(t *testing.T) { ) return []sdk.Msg{msg}, vestAccName }, - expectedCode: abci.CodeTypeOK, }, } // sign and submit the transactions for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - msgs, account := tt.msgFunc() - addr := testfactory.GetAddress(cctx.Keyring, account) - signer, err := user.SetupSigner(cctx.GoContext(), cctx.Keyring, cctx.GRPCClient, addr, ecfg) + txClient, err := user.SetupTxClient(cctx.GoContext(), cctx.Keyring, cctx.GRPCClient, ecfg) + require.NoError(t, err) + msgs, _ := tt.msgFunc() + res, err := txClient.SubmitTx(cctx.GoContext(), msgs, user.SetGasLimit(1000000), user.SetFee(2000)) require.NoError(t, err) - res, err := signer.SubmitTx(cctx.GoContext(), msgs, user.SetGasLimit(1000000), user.SetFee(2000)) - if tt.expectedCode != abci.CodeTypeOK { - require.Error(t, err) - } else { - require.NoError(t, err) - } require.NotNil(t, res) - assert.Equal(t, tt.expectedCode, res.Code, res.RawLog) + assert.Equal(t, abci.CodeTypeOK, res.Code, res.RawLog) }) } } diff --git a/app/test/priority_test.go b/app/test/priority_test.go index 35f03b91b8..5a89ca7cea 100644 --- a/app/test/priority_test.go +++ b/app/test/priority_test.go @@ -9,16 +9,19 @@ import ( "github.com/celestiaorg/celestia-app/v2/app" "github.com/celestiaorg/celestia-app/v2/app/encoding" + "github.com/celestiaorg/go-square/namespace" + "github.com/celestiaorg/celestia-app/v2/pkg/appconsts" "github.com/celestiaorg/celestia-app/v2/pkg/user" "github.com/celestiaorg/celestia-app/v2/test/util/blobfactory" "github.com/celestiaorg/celestia-app/v2/test/util/testfactory" "github.com/celestiaorg/celestia-app/v2/test/util/testnode" + blobtypes "github.com/celestiaorg/celestia-app/v2/x/blob/types" - "github.com/celestiaorg/go-square/namespace" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" tmrand "github.com/tendermint/tendermint/libs/rand" rpctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -34,9 +37,10 @@ func TestPriorityTestSuite(t *testing.T) { type PriorityTestSuite struct { suite.Suite - ecfg encoding.Config - signers []*user.Signer - cctx testnode.Context + ecfg encoding.Config + accountNames []string + txClient *user.TxClient + cctx testnode.Context rand *tmrand.Rand } @@ -44,8 +48,9 @@ type PriorityTestSuite struct { func (s *PriorityTestSuite) SetupSuite() { t := s.T() + s.accountNames = testfactory.GenerateAccounts(10) cfg := testnode.DefaultConfig(). - WithFundedAccounts(testfactory.GenerateAccounts(10)...). + WithFundedAccounts(s.accountNames...). // use a long block time to guarantee that some transactions are included in the same block WithTimeoutCommit(time.Second) @@ -56,13 +61,9 @@ func (s *PriorityTestSuite) SetupSuite() { require.NoError(t, cctx.WaitForNextBlock()) - for _, acc := range cfg.Genesis.Accounts() { - addr := sdk.AccAddress(acc.PubKey.Address()) - signer, err := user.SetupSigner(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, addr, s.ecfg) - signer.SetPollTime(time.Millisecond * 300) - require.NoError(t, err) - s.signers = append(s.signers, signer) - } + var err error + s.txClient, err = user.SetupTxClient(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, s.ecfg) + require.NoError(t, err) } // TestPriorityByGasPrice tests that transactions are sorted by gas price when @@ -73,24 +74,22 @@ func (s *PriorityTestSuite) TestPriorityByGasPrice() { t := s.T() // quickly submit blobs with a random fee - - hashes := make(chan string, len(s.signers)) + hashes := make(chan string, len(s.accountNames)) blobSize := uint32(100) gasLimit := blobtypes.DefaultEstimateGas([]uint32{blobSize}) wg := &sync.WaitGroup{} - for _, signer := range s.signers { + for _, accName := range s.accountNames { wg.Add(1) - signer := signer // new variable per iteration + accName := accName // new variable per iteration go func() { defer wg.Done() // ensure that it is greater than the min gas price gasPrice := float64(s.rand.Intn(1000)+1) * appconsts.DefaultMinGasPrice - resp, err := signer.SubmitPayForBlob( + blobs := blobfactory.ManyBlobs(s.rand, []namespace.Namespace{namespace.RandomBlobNamespace()}, []int{100}) + resp, err := s.txClient.BroadcastPayForBlobWithAccount( s.cctx.GoContext(), - blobfactory.ManyBlobs( - s.rand, - []namespace.Namespace{namespace.RandomBlobNamespace()}, - []int{100}), + accName, + blobs, user.SetGasLimitAndFee(gasLimit, gasPrice), ) require.NoError(t, err) diff --git a/app/test/process_proposal_test.go b/app/test/process_proposal_test.go index f8c03e64fc..fd52936322 100644 --- a/app/test/process_proposal_test.go +++ b/app/test/process_proposal_test.go @@ -35,8 +35,7 @@ func TestProcessProposal(t *testing.T) { accounts := testfactory.GenerateAccounts(6) testApp, kr := testutil.SetupTestAppWithGenesisValSet(app.DefaultConsensusParams(), accounts...) infos := queryAccountInfo(testApp, accounts, kr) - addr := testfactory.GetAddress(kr, accounts[0]) - signer, err := user.NewSigner(kr, nil, addr, enc, testutil.ChainID, infos[0].AccountNum, infos[0].Sequence, appconsts.LatestVersion) + signer, err := user.NewSigner(kr, enc, testutil.ChainID, appconsts.LatestVersion, user.NewAccount(accounts[0], infos[0].AccountNum, infos[0].Sequence)) require.NoError(t, err) // create 4 single blob blobTxs that are signed with valid account numbers diff --git a/app/test/square_size_test.go b/app/test/square_size_test.go index aae91c637e..28748a3bf7 100644 --- a/app/test/square_size_test.go +++ b/app/test/square_size_test.go @@ -61,13 +61,13 @@ func (s *SquareSizeIntegrationTest) SetupSuite() { // block with spam txs to measure that the desired max is getting hit func (s *SquareSizeIntegrationTest) TestSquareSizeUpperBound() { t := s.T() + const numBlocks = 10 type test struct { name string govMaxSquareSize int maxBytes int expectedMaxSquareSize int - pfbsPerBlock int } tests := []test{ @@ -76,32 +76,55 @@ func (s *SquareSizeIntegrationTest) TestSquareSizeUpperBound() { govMaxSquareSize: appconsts.DefaultGovMaxSquareSize, maxBytes: appconsts.DefaultMaxBytes, expectedMaxSquareSize: appconsts.DefaultGovMaxSquareSize, - pfbsPerBlock: 20, }, { name: "max bytes constrains square size", govMaxSquareSize: appconsts.DefaultGovMaxSquareSize, maxBytes: appconsts.DefaultMaxBytes, expectedMaxSquareSize: appconsts.DefaultGovMaxSquareSize, - pfbsPerBlock: 40, }, { name: "gov square size == hardcoded max", govMaxSquareSize: appconsts.DefaultSquareSizeUpperBound, maxBytes: appconsts.DefaultSquareSizeUpperBound * appconsts.DefaultSquareSizeUpperBound * appconsts.ContinuationSparseShareContentSize, expectedMaxSquareSize: appconsts.DefaultSquareSizeUpperBound, - pfbsPerBlock: 40, }, } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + errCh := make(chan error) + go func() { + seqs := txsim.NewBlobSequence( + txsim.NewRange(100_000, 100_000), + txsim.NewRange(1, 1), + ).Clone(100) + err := txsim.Run( + ctx, + s.grpcAddr, + s.cctx.Keyring, + encoding.MakeConfig(app.ModuleEncodingRegisters...), + txsim.DefaultOptions(). + WithSeed(rand.Int63()). + WithPollTime(time.Second). + SuppressLogs(), + seqs..., + ) + errCh <- err + }() + + require.NoError(t, s.cctx.WaitForBlocks(2)) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s.setBlockSizeParams(t, tt.govMaxSquareSize, tt.maxBytes) - start, end := s.fillBlocks(100_000, 10, tt.pfbsPerBlock, 20*time.Second) + require.NoError(t, s.cctx.WaitForBlocks(numBlocks)) // check that we're not going above the specified size and that we hit the specified size actualMaxSize := 0 - for i := start; i < end; i++ { + end, err := s.cctx.LatestHeight() + require.NoError(t, err) + for i := end - numBlocks; i < end; i++ { block, err := s.cctx.Client.Block(s.cctx.GoContext(), &i) require.NoError(t, err) require.LessOrEqual(t, block.Block.Data.SquareSize, uint64(tt.govMaxSquareSize)) @@ -114,39 +137,9 @@ func (s *SquareSizeIntegrationTest) TestSquareSizeUpperBound() { require.Equal(t, tt.expectedMaxSquareSize, actualMaxSize) }) } -} - -// fillBlock runs txsim with blob sequences using the provided -// arguments. The start and end blocks are returned. -func (s *SquareSizeIntegrationTest) fillBlocks(blobSize, blobsPerPFB, pfbsPerBlock int, period time.Duration) (start, end int64) { - t := s.T() - seqs := txsim.NewBlobSequence( - txsim.NewRange(blobSize/2, blobSize), - txsim.NewRange(blobsPerPFB/2, blobsPerPFB), - ).Clone(pfbsPerBlock) - - ctx, cancel := context.WithTimeout(context.Background(), period) - defer cancel() - - startBlock, err := s.cctx.Client.Block(s.cctx.GoContext(), nil) - require.NoError(t, err) - - _ = txsim.Run( - ctx, - s.grpcAddr, - s.cctx.Keyring, - encoding.MakeConfig(app.ModuleEncodingRegisters...), - txsim.DefaultOptions(). - WithSeed(rand.Int63()). - WithPollTime(time.Second). - SuppressLogs(), - seqs..., - ) - - endBlock, err := s.cctx.Client.Block(s.cctx.GoContext(), nil) - require.NoError(t, err) - - return startBlock.Block.Height, endBlock.Block.Height + cancel() + err := <-errCh + require.Contains(t, err.Error(), context.Canceled.Error()) } // setBlockSizeParams will use the validator account to set the square size and @@ -175,10 +168,10 @@ func (s *SquareSizeIntegrationTest) setBlockSizeParams(t *testing.T, squareSize, ) require.NoError(t, err) - signer, err := user.SetupSigner(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, addr, s.ecfg) + txClient, err := user.SetupTxClient(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, s.ecfg) require.NoError(t, err) - res, err := signer.SubmitTx(s.cctx.GoContext(), []sdk.Msg{msg}, blobfactory.DefaultTxOpts()...) + res, err := txClient.SubmitTx(s.cctx.GoContext(), []sdk.Msg{msg}, blobfactory.DefaultTxOpts()...) require.NoError(t, err) require.Equal(t, res.Code, abci.CodeTypeOK, res.RawLog) @@ -192,7 +185,7 @@ func (s *SquareSizeIntegrationTest) setBlockSizeParams(t *testing.T, squareSize, // create and submit a new vote vote := v1.NewMsgVote(testfactory.GetAddress(s.cctx.Keyring, account), gresp.Proposals[0].Id, v1.VoteOption_VOTE_OPTION_YES, "") - res, err = signer.SubmitTx(s.cctx.GoContext(), []sdk.Msg{vote}, blobfactory.DefaultTxOpts()...) + res, err = txClient.SubmitTx(s.cctx.GoContext(), []sdk.Msg{vote}, blobfactory.DefaultTxOpts()...) require.NoError(t, err) require.Equal(t, abci.CodeTypeOK, res.Code) diff --git a/app/test/std_sdk_test.go b/app/test/std_sdk_test.go index 10fe6e3405..daf5605709 100644 --- a/app/test/std_sdk_test.go +++ b/app/test/std_sdk_test.go @@ -311,11 +311,10 @@ func (s *StandardSDKIntegrationTestSuite) TestStandardSDK() { // sign and submit the transactions for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - msgs, account := tt.msgFunc() - addr := testfactory.GetAddress(s.cctx.Keyring, account) - signer, err := user.SetupSigner(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, addr, s.ecfg) + msgs, signer := tt.msgFunc() + txClient, err := user.SetupTxClient(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, s.ecfg, user.WithDefaultAccount(signer)) require.NoError(t, err) - res, err := signer.SubmitTx(s.cctx.GoContext(), msgs, blobfactory.DefaultTxOpts()...) + res, err := txClient.SubmitTx(s.cctx.GoContext(), msgs, blobfactory.DefaultTxOpts()...) if tt.expectedCode != abci.CodeTypeOK { require.Error(t, err) } else { diff --git a/pkg/proof/proof_test.go b/pkg/proof/proof_test.go index 0577e3792d..2064e63cf2 100644 --- a/pkg/proof/proof_test.go +++ b/pkg/proof/proof_test.go @@ -194,15 +194,15 @@ func TestNewShareInclusionProof(t *testing.T) { }, { name: "blob shares for first namespace", - startingShare: 55, - endingShare: 57, + startingShare: 56, + endingShare: 58, namespaceID: ns1, expectErr: false, }, { name: "blob shares for third namespace", - startingShare: 59, - endingShare: 61, + startingShare: 60, + endingShare: 62, namespaceID: ns3, expectErr: false, }, diff --git a/pkg/user/account.go b/pkg/user/account.go new file mode 100644 index 0000000000..8ab1d031f4 --- /dev/null +++ b/pkg/user/account.go @@ -0,0 +1,79 @@ +package user + +import ( + "context" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "google.golang.org/grpc" +) + +type Account struct { + name string + address types.AccAddress + pubKey cryptotypes.PubKey + accountNumber uint64 + + // the signers local view of the sequence number + sequence uint64 +} + +func NewAccount(keyName string, accountNumber, sequenceNumber uint64) *Account { + return &Account{ + name: keyName, + accountNumber: accountNumber, + sequence: sequenceNumber, + } +} + +func (a Account) Name() string { + return a.name +} + +func (a Account) Address() types.AccAddress { + return a.address +} + +func (a Account) PubKey() cryptotypes.PubKey { + return a.pubKey +} + +// Sequence returns the sequence number of the account. +// This is locally tracked +func (a Account) Sequence() uint64 { + return a.sequence +} + +func (a *Account) Copy() *Account { + return &Account{ + name: a.name, + address: a.address, + pubKey: a.pubKey, + accountNumber: a.accountNumber, + sequence: a.sequence, + } +} + +// QueryAccount fetches the account number and sequence number from the celestia-app node. +func QueryAccount(ctx context.Context, conn *grpc.ClientConn, registry codectypes.InterfaceRegistry, address types.AccAddress) (accNum uint64, seqNum uint64, err error) { + qclient := authtypes.NewQueryClient(conn) + // TODO: ideally we add a way to prove that the accounts rather than simply trusting the full node we are connected with + resp, err := qclient.Account( + ctx, + &authtypes.QueryAccountRequest{Address: address.String()}, + ) + if err != nil { + return accNum, seqNum, err + } + + var acc authtypes.AccountI + err = registry.UnpackAny(resp.Account, &acc) + if err != nil { + return accNum, seqNum, err + } + + accNum, seqNum = acc.GetAccountNumber(), acc.GetSequence() + return accNum, seqNum, nil +} diff --git a/pkg/user/e2e_test.go b/pkg/user/e2e_test.go index e906929b2f..3206e77b2a 100644 --- a/pkg/user/e2e_test.go +++ b/pkg/user/e2e_test.go @@ -29,7 +29,7 @@ func TestConcurrentTxSubmission(t *testing.T) { require.NoError(t, err) // Setup signer - signer, err := testnode.NewSingleSignerFromContext(ctx) + txClient, err := testnode.NewTxClientFromContext(ctx) require.NoError(t, err) // Pregenerate all the blobs @@ -49,7 +49,7 @@ func TestConcurrentTxSubmission(t *testing.T) { wg.Add(1) go func(b *blob.Blob) { defer wg.Done() - _, err := signer.SubmitPayForBlob(subCtx, []*blob.Blob{b}, user.SetGasLimitAndFee(500_000, appconsts.DefaultMinGasPrice)) + _, err := txClient.SubmitPayForBlob(subCtx, []*blob.Blob{b}, user.SetGasLimitAndFee(500_000, appconsts.DefaultMinGasPrice)) if err != nil && !errors.Is(err, context.Canceled) { // only catch the first error select { @@ -68,32 +68,3 @@ func TestConcurrentTxSubmission(t *testing.T) { default: } } - -func TestSignerTwins(t *testing.T) { - // Ref: https://github.com/celestiaorg/celestia-app/issues/3256 - t.Skip() - - // Setup network - tmConfig := testnode.DefaultTendermintConfig() - tmConfig.Consensus.TimeoutCommit = 10 * time.Second - ctx, _, _ := testnode.NewNetwork(t, testnode.DefaultConfig().WithTendermintConfig(tmConfig)) - _, err := ctx.WaitForHeight(1) - require.NoError(t, err) - - signer1, err := testnode.NewSingleSignerFromContext(ctx) - require.NoError(t, err) - signer2, err := testnode.NewSingleSignerFromContext(ctx) - require.NoError(t, err) - - blobs := blobfactory.ManyRandBlobs(tmrand.NewRand(), blobfactory.Repeat(2048, 8)...) - - _, err = signer1.SubmitPayForBlob(ctx.GoContext(), blobs[:1], user.SetGasLimitAndFee(500_000, appconsts.DefaultMinGasPrice)) - require.NoError(t, err) - - _, err = signer2.SubmitPayForBlob(ctx.GoContext(), blobs[1:3], user.SetGasLimitAndFee(500_000, appconsts.DefaultMinGasPrice)) - require.NoError(t, err) - - signer1.ForceSetSequence(4) - _, err = signer1.SubmitPayForBlob(ctx.GoContext(), blobs[3:5], user.SetGasLimitAndFee(500_000, appconsts.DefaultMinGasPrice)) - require.NoError(t, err) -} diff --git a/pkg/user/signer.go b/pkg/user/signer.go index fb9676536b..a925028dd9 100644 --- a/pkg/user/signer.go +++ b/pkg/user/signer.go @@ -1,260 +1,104 @@ package user import ( - "context" "errors" "fmt" - "strings" - "sync" - "time" "github.com/celestiaorg/go-square/blob" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" "github.com/cosmos/cosmos-sdk/crypto/keyring" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdktypes "github.com/cosmos/cosmos-sdk/types" - sdktx "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - abci "github.com/tendermint/tendermint/abci/types" - "google.golang.org/grpc" - "github.com/celestiaorg/celestia-app/v2/app/encoding" - apperrors "github.com/celestiaorg/celestia-app/v2/app/errors" blobtypes "github.com/celestiaorg/celestia-app/v2/x/blob/types" ) -const ( - DefaultPollTime = 3 * time.Second - DefaultGasMultiplier float64 = 1.1 -) - -type Option func(s *Signer) - -// WithGasMultiplier is a functional option allows to configure the gas multiplier. -func WithGasMultiplier(multiplier float64) Option { - return func(s *Signer) { - s.gasMultiplier = multiplier - } -} - -// Signer is an abstraction for building, signing, and broadcasting Celestia transactions +// Signer is struct for building and signing Celestia transactions +// It supports multiple accounts wrapping a Keyring. +// NOTE: All transactions may only have a single signer +// Signer is not thread-safe. type Signer struct { - keys keyring.Keyring - address sdktypes.AccAddress - enc client.TxConfig - grpc *grpc.ClientConn - pk cryptotypes.PubKey - chainID string - accountNumber uint64 + keys keyring.Keyring + enc client.TxConfig + chainID string // FIXME: the signer is currently incapable of detecting an appversion // change and could produce incorrect PFBs if it the network is at an // appVersion that the signer does not support appVersion uint64 - mtx sync.RWMutex - // how often to poll the network for confirmation of a transaction - pollTime time.Duration - // the signers local view of the sequence number - localSequence uint64 - // the chains last known sequence number - networkSequence uint64 - // gasMultiplier is used to increase gas limit as it is sometimes underestimated - gasMultiplier float64 - // lookup map of all pending and yet to be confirmed outbound transactions - outboundSequences map[uint64]struct{} - // a reverse map for confirming which sequence numbers have been committed - reverseTxHashSequenceMap map[string]uint64 + // set of accounts that the signer can manage. Should match the keys on the keyring + accounts map[string]*Account + addressToAccountMap map[string]string } // NewSigner returns a new signer using the provided keyring +// There must be at least one account in the keyring +// The first account provided will be set as the default func NewSigner( keys keyring.Keyring, - conn *grpc.ClientConn, - address sdktypes.AccAddress, - enc client.TxConfig, + encCfg client.TxConfig, chainID string, - accountNumber, sequence, appVersion uint64, - options ...Option, -) (*Signer, error) { - // check that the address exists - record, err := keys.KeyByAddress(address) - if err != nil { - return nil, err - } - - pk, err := record.GetPubKey() - if err != nil { - return nil, err - } - - signer := &Signer{ - keys: keys, - address: address, - grpc: conn, - enc: enc, - pk: pk, - chainID: chainID, - accountNumber: accountNumber, - appVersion: appVersion, - localSequence: sequence, - networkSequence: sequence, - pollTime: DefaultPollTime, - gasMultiplier: DefaultGasMultiplier, - outboundSequences: make(map[uint64]struct{}), - reverseTxHashSequenceMap: make(map[string]uint64), - } - - for _, opt := range options { - opt(signer) - } - return signer, nil -} - -// SetupSingleSigner sets up a signer based on the provided keyring. The keyring -// must contain exactly one key. It extracts the address from the key and uses -// the grpc connection to populate the chainID, account number, and sequence -// number. -func SetupSingleSigner( - ctx context.Context, - keys keyring.Keyring, - conn *grpc.ClientConn, - encCfg encoding.Config, - options ...Option, + accounts ...*Account, ) (*Signer, error) { - records, err := keys.List() - if err != nil { - return nil, err - } - - if len(records) != 1 { - return nil, errors.New("keyring must contain exactly one key") - } - - address, err := records[0].GetAddress() - if err != nil { - return nil, err - } - - return SetupSigner(ctx, keys, conn, address, encCfg, options...) -} - -// SetupSigner uses the underlying grpc connection to populate the chainID, accountNumber and sequence number of the -// account. -func SetupSigner( - ctx context.Context, - keys keyring.Keyring, - conn *grpc.ClientConn, - address sdktypes.AccAddress, - encCfg encoding.Config, - options ...Option, -) (*Signer, error) { - resp, err := tmservice.NewServiceClient(conn).GetLatestBlock( - ctx, - &tmservice.GetLatestBlockRequest{}, - ) - if err != nil { - return nil, err - } - - chainID := resp.SdkBlock.Header.ChainID - appVersion := resp.SdkBlock.Header.Version.App - accNum, seqNum, err := QueryAccount(ctx, conn, encCfg, address.String()) - if err != nil { - return nil, err + s := &Signer{ + keys: keys, + chainID: chainID, + enc: encCfg, + accounts: make(map[string]*Account), + addressToAccountMap: make(map[string]string), + appVersion: appVersion, } - return NewSigner(keys, conn, address, encCfg.TxConfig, chainID, accNum, seqNum, appVersion, options...) -} - -// SubmitTx forms a transaction from the provided messages, signs it, and submits it to the chain. TxOptions -// may be provided to set the fee and gas limit. -func (s *Signer) SubmitTx(ctx context.Context, msgs []sdktypes.Msg, opts ...TxOption) (*sdktypes.TxResponse, error) { - tx, err := s.CreateTx(msgs, opts...) - if err != nil { - return nil, err - } - - resp, err := s.BroadcastTx(ctx, tx) - if err != nil { - return resp, err - } - - return s.ConfirmTx(ctx, resp.TxHash) -} - -// SubmitPayForBlob forms a transaction from the provided blobs, signs it, and submits it to the chain. -// TxOptions may be provided to set the fee and gas limit. -func (s *Signer) SubmitPayForBlob(ctx context.Context, blobs []*blob.Blob, opts ...TxOption) (*sdktypes.TxResponse, error) { - resp, err := s.BroadcastPayForBlob(ctx, blobs, opts...) - if err != nil { - return resp, err + for _, acc := range accounts { + if err := s.AddAccount(acc); err != nil { + return nil, err + } } - return s.ConfirmTx(ctx, resp.TxHash) + return s, nil } -// BroadcastPayForBlob forms a transaction from the provided blobs, signs it, -// and broadcasts it to the chain. TxOptions may be provided to set the fee and -// gas limit. This function does not block on the tx actually being included in -// a block. -func (s *Signer) BroadcastPayForBlob(ctx context.Context, blobs []*blob.Blob, opts ...TxOption) (*sdktypes.TxResponse, error) { - s.mtx.Lock() - defer s.mtx.Unlock() - txBytes, seqNum, err := s.createPayForBlobs(blobs, opts...) +// CreateTx forms a transaction from the provided messages and signs it. +// TxOptions may be optionally used to set the gas limit and fee. +func (s *Signer) CreateTx(msgs []sdktypes.Msg, opts ...TxOption) ([]byte, error) { + tx, _, _, err := s.SignTx(msgs, opts...) if err != nil { return nil, err } - - return s.broadcastTx(ctx, txBytes, seqNum) + return s.EncodeTx(tx) } -// CreateTx forms a transaction from the provided messages and signs it. TxOptions may be optionally -// used to set the gas limit and fee. -func (s *Signer) CreateTx(msgs []sdktypes.Msg, opts ...TxOption) (authsigning.Tx, error) { - s.mtx.Lock() - defer s.mtx.Unlock() - - return s.createTx(msgs, opts...) -} - -func (s *Signer) createTx(msgs []sdktypes.Msg, opts ...TxOption) (authsigning.Tx, error) { +func (s *Signer) SignTx(msgs []sdktypes.Msg, opts ...TxOption) (authsigning.Tx, string, uint64, error) { txBuilder := s.txBuilder(opts...) if err := txBuilder.SetMsgs(msgs...); err != nil { - return nil, err + return nil, "", 0, err } - if err := s.signTransaction(txBuilder, s.getAndIncrementSequence()); err != nil { - return nil, err + signer, sequence, err := s.signTransaction(txBuilder) + if err != nil { + return nil, "", 0, err } - return txBuilder.GetTx(), nil + return txBuilder.GetTx(), signer, sequence, nil } -func (s *Signer) CreatePayForBlob(blobs []*blob.Blob, opts ...TxOption) ([]byte, error) { - s.mtx.Lock() - defer s.mtx.Unlock() - blobTx, _, err := s.createPayForBlobs(blobs, opts...) - return blobTx, err -} - -func (s *Signer) createPayForBlobs(blobs []*blob.Blob, opts ...TxOption) ([]byte, uint64, error) { - msg, err := blobtypes.NewMsgPayForBlobs(s.address.String(), s.appVersion, blobs...) - if err != nil { - return nil, 0, err +func (s *Signer) CreatePayForBlobs(accountName string, blobs []*blob.Blob, opts ...TxOption) ([]byte, uint64, error) { + acc, exists := s.accounts[accountName] + if !exists { + return nil, 0, fmt.Errorf("account %s not found", accountName) } - tx, err := s.createTx([]sdktypes.Msg{msg}, opts...) + msg, err := blobtypes.NewMsgPayForBlobs(acc.address.String(), s.appVersion, blobs...) if err != nil { return nil, 0, err } - seqNum, err := getSequenceNumber(tx) + tx, _, sequence, err := s.SignTx([]sdktypes.Msg{msg}, opts...) if err != nil { - panic(err) + return nil, 0, err } txBytes, err := s.EncodeTx(tx) @@ -263,7 +107,7 @@ func (s *Signer) createPayForBlobs(blobs []*blob.Blob, opts ...TxOption) ([]byte } blobTx, err := blob.MarshalBlobTx(txBytes, blobs...) - return blobTx, seqNum, err + return blobTx, sequence, err } func (s *Signer) EncodeTx(tx sdktypes.Tx) ([]byte, error) { @@ -282,255 +126,91 @@ func (s *Signer) DecodeTx(txBytes []byte) (authsigning.Tx, error) { return authTx, nil } -// BroadcastTx submits the provided transaction bytes to the chain and returns the response. -func (s *Signer) BroadcastTx(ctx context.Context, tx authsigning.Tx) (*sdktypes.TxResponse, error) { - s.mtx.Lock() - defer s.mtx.Unlock() - txBytes, err := s.EncodeTx(tx) - if err != nil { - return nil, err - } - sequence, err := getSequenceNumber(tx) - if err != nil { - return nil, err - } - return s.broadcastTx(ctx, txBytes, sequence) +// ChainID returns the chain ID of the signer. +func (s *Signer) ChainID() string { + return s.chainID } -// CONTRACT: assumes the caller has the lock -func (s *Signer) broadcastTx(ctx context.Context, txBytes []byte, sequence uint64) (*sdktypes.TxResponse, error) { - if _, exists := s.outboundSequences[sequence]; exists { - return s.retryBroadcastingTx(ctx, txBytes, sequence+1) - } - - if sequence < s.networkSequence { - s.localSequence = s.networkSequence - return s.retryBroadcastingTx(ctx, txBytes, s.localSequence) - } +// Account returns an account of the signer from the key name +func (s *Signer) Account(name string) *Account { + return s.accounts[name] +} - txClient := sdktx.NewServiceClient(s.grpc) - resp, err := txClient.BroadcastTx( - ctx, - &sdktx.BroadcastTxRequest{ - Mode: sdktx.BroadcastMode_BROADCAST_MODE_SYNC, - TxBytes: txBytes, - }, - ) - if err != nil { - return nil, err - } - if apperrors.IsNonceMismatchCode(resp.TxResponse.Code) { - // extract what the lastCommittedNonce on chain is - nextSequence, err := apperrors.ParseExpectedSequence(resp.TxResponse.RawLog) - if err != nil { - return nil, fmt.Errorf("parsing nonce mismatch upon retry: %w", err) - } - s.networkSequence = nextSequence - s.localSequence = nextSequence - // FIXME: We can't actually resign the transaction. A malicious node - // may manipulate us into signing the same transaction several times - // and then executing them. We need some proof of what the last network - // sequence is rather than relying on an error provided by the node - // return s.retryBroadcastingTx(ctx, txBytes, nextSequence) - // Ref: https://github.com/celestiaorg/celestia-app/issues/3256 - } else if resp.TxResponse.Code == abci.CodeTypeOK { - s.outboundSequences[sequence] = struct{}{} - s.reverseTxHashSequenceMap[resp.TxResponse.TxHash] = sequence - return resp.TxResponse, nil +// AccountByAddress returns the account associated with the given address +func (s *Signer) AccountByAddress(address sdktypes.AccAddress) *Account { + accountName, exists := s.addressToAccountMap[address.String()] + if !exists { + return nil } - return resp.TxResponse, fmt.Errorf("tx failed with code %d: %s", resp.TxResponse.Code, resp.TxResponse.RawLog) + return s.accounts[accountName] } -// retryBroadcastingTx creates a new transaction by copying over an existing transaction but creates a new signature with the -// new sequence number. It then calls `broadcastTx` and attempts to submit the transaction -func (s *Signer) retryBroadcastingTx(ctx context.Context, txBytes []byte, newSequenceNumber uint64) (*sdktypes.TxResponse, error) { - blobTx, isBlobTx := blob.UnmarshalBlobTx(txBytes) - if isBlobTx { - txBytes = blobTx.Tx - } - tx, err := s.DecodeTx(txBytes) - if err != nil { - return nil, err - } - txBuilder := s.txBuilder() - if err := txBuilder.SetMsgs(tx.GetMsgs()...); err != nil { - return nil, err - } - if granter := tx.FeeGranter(); granter != nil { - txBuilder.SetFeeGranter(granter) - } - if payer := tx.FeePayer(); payer != nil { - txBuilder.SetFeePayer(payer) - } - if memo := tx.GetMemo(); memo != "" { - txBuilder.SetMemo(memo) - } - if fee := tx.GetFee(); fee != nil { - txBuilder.SetFeeAmount(fee) - } - if gas := tx.GetGas(); gas > 0 { - txBuilder.SetGasLimit(gas) +func (s *Signer) Accounts() []*Account { + accounts := make([]*Account, len(s.accounts)) + i := 0 + for _, acc := range s.accounts { + accounts[i] = acc + i++ } + return accounts +} - if err := s.signTransaction(txBuilder, newSequenceNumber); err != nil { - return nil, fmt.Errorf("resigning transaction: %w", err) +func (s *Signer) findAccount(txbuilder client.TxBuilder) (*Account, error) { + signers := txbuilder.GetTx().GetSigners() + if len(signers) == 0 { + return nil, fmt.Errorf("message has no signer") } - - newTxBytes, err := s.EncodeTx(txBuilder.GetTx()) - if err != nil { - return nil, err + accountName, exists := s.addressToAccountMap[signers[0].String()] + if !exists { + return nil, fmt.Errorf("account %s not found", signers[0].String()) } + return s.accounts[accountName], nil +} - // rewrap the blob tx if it was originally a blob tx - if isBlobTx { - newTxBytes, err = blob.MarshalBlobTx(newTxBytes, blobTx.Blobs...) - if err != nil { - return nil, err - } +func (s *Signer) IncrementSequence(accountName string) error { + acc, exists := s.accounts[accountName] + if !exists { + return fmt.Errorf("account %s does not exist", accountName) } - - return s.broadcastTx(ctx, newTxBytes, newSequenceNumber) + acc.sequence++ + return nil } -// ConfirmTx periodically pings the provided node for the commitment of a transaction by its -// hash. It will continually loop until the context is cancelled, the tx is found or an error -// is encountered. -func (s *Signer) ConfirmTx(ctx context.Context, txHash string) (*sdktypes.TxResponse, error) { - txClient := sdktx.NewServiceClient(s.grpc) - - pollTicker := time.NewTicker(s.getPollTime()) - defer pollTicker.Stop() - - for { - resp, err := txClient.GetTx(ctx, &sdktx.GetTxRequest{Hash: txHash}) - if err == nil { - if resp.TxResponse.Code != 0 { - s.updateNetworkSequence(txHash, false) - return resp.TxResponse, fmt.Errorf("tx was included but failed with code %d: %s", resp.TxResponse.Code, resp.TxResponse.RawLog) - } - s.updateNetworkSequence(txHash, true) - return resp.TxResponse, nil - } - // FIXME: this is a relatively brittle of working out whether to retry or not. The tx might be not found for other - // reasons. It may have been removed from the mempool at a later point. We should build an endpoint that gives the - // signer more information on the status of their transaction and then update the logic here - if !strings.Contains(err.Error(), "not found") { - return &sdktypes.TxResponse{}, err - } - - // Wait for the next round. - select { - case <-ctx.Done(): - return &sdktypes.TxResponse{}, ctx.Err() - case <-pollTicker.C: - } +func (s *Signer) SetSequence(accountName string, seq uint64) error { + acc, exists := s.accounts[accountName] + if !exists { + return fmt.Errorf("account %s does not exist", accountName) } + + acc.sequence = seq + return nil } -func (s *Signer) EstimateGas(ctx context.Context, msgs []sdktypes.Msg, opts ...TxOption) (uint64, error) { - txBuilder := s.txBuilder(opts...) - if err := txBuilder.SetMsgs(msgs...); err != nil { - return 0, err +func (s *Signer) AddAccount(acc *Account) error { + if acc == nil { + return errors.New("account is nil") } - if err := s.signTransaction(txBuilder, s.LocalSequence()); err != nil { - return 0, err + record, err := s.keys.Key(acc.name) + if err != nil { + return fmt.Errorf("retrieving key for account %s: %w", acc.name, err) } - txBytes, err := s.enc.TxEncoder()(txBuilder.GetTx()) + addr, err := record.GetAddress() if err != nil { - return 0, err + return fmt.Errorf("getting address for key %s: %w", acc.pubKey, err) } - resp, err := sdktx.NewServiceClient(s.grpc).Simulate(ctx, &sdktx.SimulateRequest{ - TxBytes: txBytes, - }) + pk, err := record.GetPubKey() if err != nil { - return 0, err + return fmt.Errorf("getting public key for account %s: %w", acc.name, err) } - gasLimit := uint64(float64(resp.GasInfo.GasUsed) * s.gasMultiplier) - return gasLimit, nil -} - -// ChainID returns the chain ID of the signer. -func (s *Signer) ChainID() string { - return s.chainID -} - -// AccountNumber returns the account number of the signer. -func (s *Signer) AccountNumber() uint64 { - return s.accountNumber -} - -// Address returns the address of the signer. -func (s *Signer) Address() sdktypes.AccAddress { - return s.address -} - -// SetPollTime sets how often the signer should poll for the confirmation of the transaction -func (s *Signer) SetPollTime(pollTime time.Duration) { - s.mtx.Lock() - defer s.mtx.Unlock() - s.pollTime = pollTime -} - -func (s *Signer) getPollTime() time.Duration { - s.mtx.Lock() - defer s.mtx.Unlock() - return s.pollTime -} - -// PubKey returns the public key of the signer -func (s *Signer) PubKey() cryptotypes.PubKey { - return s.pk -} - -// LocalSequence returns the next sequence number of the signers -// locally saved -func (s *Signer) LocalSequence() uint64 { - s.mtx.RLock() - defer s.mtx.RUnlock() - return s.localSequence -} - -func (s *Signer) NetworkSequence() uint64 { - s.mtx.RLock() - defer s.mtx.RUnlock() - return s.networkSequence -} - -// getAndIncrementSequence gets the latest signed sequence and increments the -// local sequence number -func (s *Signer) getAndIncrementSequence() uint64 { - defer func() { s.localSequence++ }() - return s.localSequence -} - -// ForceSetSequence manually overrides the current local and network level -// sequence number. Be careful when invoking this as it may cause the -// transactions to reject the sequence if it doesn't match the one in state -func (s *Signer) ForceSetSequence(seq uint64) { - s.mtx.Lock() - defer s.mtx.Unlock() - s.localSequence = seq - s.networkSequence = seq -} - -// updateNetworkSequence is called once a transaction is confirmed -// and updates the chains last known sequence number -func (s *Signer) updateNetworkSequence(txHash string, success bool) { - s.mtx.Lock() - defer s.mtx.Unlock() - sequence, exists := s.reverseTxHashSequenceMap[txHash] - if !exists { - return - } - if success && sequence >= s.networkSequence { - s.networkSequence = sequence + 1 - } - delete(s.outboundSequences, sequence) - delete(s.reverseTxHashSequenceMap, txHash) + acc.address = addr + acc.pubKey = pk + s.accounts[acc.name] = acc + s.addressToAccountMap[addr.String()] = acc.name + return nil } // Keyring exposes the signers underlying keyring @@ -538,44 +218,40 @@ func (s *Signer) Keyring() keyring.Keyring { return s.keys } -func (s *Signer) signTransaction(builder client.TxBuilder, sequence uint64) error { - signers := builder.GetTx().GetSigners() - if len(signers) != 1 { - return fmt.Errorf("expected 1 signer, got %d", len(signers)) - } - - if !s.address.Equals(signers[0]) { - return fmt.Errorf("expected signer %s, got %s", s.address.String(), signers[0].String()) +func (s *Signer) signTransaction(builder client.TxBuilder) (string, uint64, error) { + account, err := s.findAccount(builder) + if err != nil { + return "", 0, err } // To ensure we have the correct bytes to sign over we produce // a dry run of the signing data - err := builder.SetSignatures(s.getSignatureV2(sequence, nil)) + err = builder.SetSignatures(s.getSignatureV2(account.sequence, account.pubKey, nil)) if err != nil { - return fmt.Errorf("error setting draft signatures: %w", err) + return "", 0, fmt.Errorf("error setting draft signatures: %w", err) } // now we can use the data to produce the signature from the signer - signature, err := s.createSignature(builder, sequence) + signature, err := s.createSignature(builder, account, account.sequence) if err != nil { - return fmt.Errorf("error creating signature: %w", err) + return "", 0, fmt.Errorf("error creating signature: %w", err) } - err = builder.SetSignatures(s.getSignatureV2(sequence, signature)) + err = builder.SetSignatures(s.getSignatureV2(account.sequence, account.pubKey, signature)) if err != nil { - return fmt.Errorf("error setting signatures: %w", err) + return "", 0, fmt.Errorf("error setting signatures: %w", err) } - return nil + return account.name, account.sequence, nil } -func (s *Signer) createSignature(builder client.TxBuilder, sequence uint64) ([]byte, error) { +func (s *Signer) createSignature(builder client.TxBuilder, account *Account, sequence uint64) ([]byte, error) { signerData := authsigning.SignerData{ - Address: s.address.String(), + Address: account.address.String(), ChainID: s.ChainID(), - AccountNumber: s.accountNumber, + AccountNumber: account.accountNumber, Sequence: sequence, - PubKey: s.pk, + PubKey: account.pubKey, } bytesToSign, err := s.enc.SignModeHandler().GetSignBytes( @@ -587,7 +263,7 @@ func (s *Signer) createSignature(builder client.TxBuilder, sequence uint64) ([]b return nil, fmt.Errorf("error getting sign bytes: %w", err) } - signature, _, err := s.keys.SignByAddress(s.address, bytesToSign) + signature, _, err := s.keys.Sign(account.name, bytesToSign) if err != nil { return nil, fmt.Errorf("error signing bytes: %w", err) } @@ -604,28 +280,7 @@ func (s *Signer) txBuilder(opts ...TxOption) client.TxBuilder { return builder } -// QueryAccount fetches the account number and sequence number from the celestia-app node. -func QueryAccount(ctx context.Context, conn *grpc.ClientConn, encCfg encoding.Config, address string) (accNum uint64, seqNum uint64, err error) { - qclient := authtypes.NewQueryClient(conn) - resp, err := qclient.Account( - ctx, - &authtypes.QueryAccountRequest{Address: address}, - ) - if err != nil { - return accNum, seqNum, err - } - - var acc authtypes.AccountI - err = encCfg.InterfaceRegistry.UnpackAny(resp.Account, &acc) - if err != nil { - return accNum, seqNum, err - } - - accNum, seqNum = acc.GetAccountNumber(), acc.GetSequence() - return accNum, seqNum, nil -} - -func (s *Signer) getSignatureV2(sequence uint64, signature []byte) signing.SignatureV2 { +func (s *Signer) getSignatureV2(sequence uint64, pubKey cryptotypes.PubKey, signature []byte) signing.SignatureV2 { sigV2 := signing.SignatureV2{ Data: &signing.SingleSignatureData{ SignMode: signing.SignMode_SIGN_MODE_DIRECT, @@ -634,19 +289,7 @@ func (s *Signer) getSignatureV2(sequence uint64, signature []byte) signing.Signa Sequence: sequence, } if sequence == 0 { - sigV2.PubKey = s.pk + sigV2.PubKey = pubKey } return sigV2 } - -func getSequenceNumber(tx authsigning.Tx) (uint64, error) { - sigs, err := tx.GetSignaturesV2() - if err != nil { - return 0, err - } - if len(sigs) > 1 { - return 0, fmt.Errorf("only a signle signature is supported, got %d", len(sigs)) - } - - return sigs[0].Sequence, nil -} diff --git a/pkg/user/tx_client.go b/pkg/user/tx_client.go new file mode 100644 index 0000000000..b4183bc52c --- /dev/null +++ b/pkg/user/tx_client.go @@ -0,0 +1,462 @@ +package user + +import ( + "bytes" + "context" + "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/celestiaorg/go-square/blob" + "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdktypes "github.com/cosmos/cosmos-sdk/types" + sdktx "github.com/cosmos/cosmos-sdk/types/tx" + abci "github.com/tendermint/tendermint/abci/types" + "google.golang.org/grpc" + + "github.com/celestiaorg/celestia-app/v2/app/encoding" + apperrors "github.com/celestiaorg/celestia-app/v2/app/errors" +) + +const ( + DefaultPollTime = 3 * time.Second + DefaultGasMultiplier float64 = 1.1 +) + +type Option func(s *TxClient) + +// WithGasMultiplier is a functional option allows to configure the gas multiplier. +func WithGasMultiplier(multiplier float64) Option { + return func(c *TxClient) { + c.gasMultiplier = multiplier + } +} + +func WithPollTime(time time.Duration) Option { + return func(c *TxClient) { + c.pollTime = time + } +} + +func WithDefaultAddress(address sdktypes.AccAddress) Option { + return func(c *TxClient) { + record, err := c.signer.keys.KeyByAddress(address) + if err != nil { + panic(err) + } + c.defaultAccount = record.Name + } +} + +func WithDefaultAccount(name string) Option { + return func(c *TxClient) { + if _, err := c.signer.keys.Key(name); err != nil { + panic(err) + } + c.defaultAccount = name + } +} + +// TxClient is an abstraction for building, signing, and broadcasting Celestia transactions +// It supports multiple accounts. If none is specified, it will +// try use the default account. +// TxClient is thread-safe. +type TxClient struct { + mtx sync.Mutex + signer *Signer + registry codectypes.InterfaceRegistry + grpc *grpc.ClientConn + // how often to poll the network for confirmation of a transaction + pollTime time.Duration + // gasMultiplier is used to increase gas limit as it is sometimes underestimated + gasMultiplier float64 + defaultAccount string + defaultAddress sdktypes.AccAddress +} + +// NewTxClient returns a new signer using the provided keyring +func NewTxClient( + signer *Signer, + conn *grpc.ClientConn, + registry codectypes.InterfaceRegistry, + options ...Option, +) (*TxClient, error) { + records, err := signer.keys.List() + if err != nil { + return nil, fmt.Errorf("retrieving keys: %w", err) + } + + if len(records) == 0 { + return nil, errors.New("signer must have at least one key") + } + + addr, err := records[0].GetAddress() + if err != nil { + return nil, err + } + + txClient := &TxClient{ + signer: signer, + registry: registry, + grpc: conn, + pollTime: DefaultPollTime, + gasMultiplier: DefaultGasMultiplier, + defaultAccount: records[0].Name, + defaultAddress: addr, + } + + for _, opt := range options { + opt(txClient) + } + + return txClient, nil +} + +// SetupTxClient uses the underlying grpc connection to populate the chainID, accountNumber and sequence number of all +// the accounts in the keyring. +func SetupTxClient( + ctx context.Context, + keys keyring.Keyring, + conn *grpc.ClientConn, + encCfg encoding.Config, + options ...Option, +) (*TxClient, error) { + resp, err := tmservice.NewServiceClient(conn).GetLatestBlock( + ctx, + &tmservice.GetLatestBlockRequest{}, + ) + if err != nil { + return nil, err + } + + chainID := resp.SdkBlock.Header.ChainID + appVersion := resp.SdkBlock.Header.Version.App + + records, err := keys.List() + if err != nil { + return nil, err + } + + accounts := make([]*Account, 0, len(records)) + for _, record := range records { + addr, err := record.GetAddress() + if err != nil { + return nil, err + } + accNum, seqNum, err := QueryAccount(ctx, conn, encCfg.InterfaceRegistry, addr) + if err != nil { + // skip over the accounts that don't exist in state + continue + } + + accounts = append(accounts, NewAccount(record.Name, accNum, seqNum)) + } + + signer, err := NewSigner(keys, encCfg.TxConfig, chainID, appVersion, accounts...) + if err != nil { + return nil, fmt.Errorf("failed to create signer: %w", err) + } + + return NewTxClient(signer, conn, encCfg.InterfaceRegistry, options...) +} + +// SubmitPayForBlob forms a transaction from the provided blobs, signs it, and submits it to the chain. +// TxOptions may be provided to set the fee and gas limit. +func (s *TxClient) SubmitPayForBlob(ctx context.Context, blobs []*blob.Blob, opts ...TxOption) (*sdktypes.TxResponse, error) { + resp, err := s.BroadcastPayForBlob(ctx, blobs, opts...) + if err != nil { + return resp, err + } + + return s.ConfirmTx(ctx, resp.TxHash) +} + +func (s *TxClient) SubmitPayForBlobsWithAccount(ctx context.Context, account string, blobs []*blob.Blob, opts ...TxOption) (*sdktypes.TxResponse, error) { + resp, err := s.BroadcastPayForBlobWithAccount(ctx, account, blobs, opts...) + if err != nil { + return resp, err + } + + return s.ConfirmTx(ctx, resp.TxHash) +} + +// BroadcastPayForBlob signs and broadcasts a transaction to pay for blobs. +// It does not confirm that the transaction has been committed on chain. +func (s *TxClient) BroadcastPayForBlob(ctx context.Context, blobs []*blob.Blob, opts ...TxOption) (*sdktypes.TxResponse, error) { + return s.BroadcastPayForBlobWithAccount(ctx, s.defaultAccount, blobs, opts...) +} + +func (s *TxClient) BroadcastPayForBlobWithAccount(ctx context.Context, account string, blobs []*blob.Blob, opts ...TxOption) (*sdktypes.TxResponse, error) { + s.mtx.Lock() + defer s.mtx.Unlock() + if err := s.checkAccountLoaded(ctx, account); err != nil { + return nil, err + } + + txBytes, _, err := s.signer.CreatePayForBlobs(account, blobs, opts...) + if err != nil { + return nil, err + } + + return s.broadcastTx(ctx, txBytes, account) +} + +// SubmitTx forms a transaction from the provided messages, signs it, and submits it to the chain. TxOptions +// may be provided to set the fee and gas limit. +func (s *TxClient) SubmitTx(ctx context.Context, msgs []sdktypes.Msg, opts ...TxOption) (*sdktypes.TxResponse, error) { + resp, err := s.BroadcastTx(ctx, msgs, opts...) + if err != nil { + return resp, err + } + + return s.ConfirmTx(ctx, resp.TxHash) +} + +func (s *TxClient) BroadcastTx(ctx context.Context, msgs []sdktypes.Msg, opts ...TxOption) (*sdktypes.TxResponse, error) { + s.mtx.Lock() + defer s.mtx.Unlock() + account, err := s.getAccountNameFromMsgs(msgs) + if err != nil { + return nil, err + } + + if err := s.checkAccountLoaded(ctx, account); err != nil { + return nil, err + } + + tx, account, _, err := s.signer.SignTx(msgs, opts...) + if err != nil { + return nil, err + } + + txBytes, err := s.signer.EncodeTx(tx) + if err != nil { + return nil, err + } + + return s.broadcastTx(ctx, txBytes, account) +} + +func (s *TxClient) broadcastTx(ctx context.Context, txBytes []byte, signer string) (*sdktypes.TxResponse, error) { + txClient := sdktx.NewServiceClient(s.grpc) + resp, err := txClient.BroadcastTx( + ctx, + &sdktx.BroadcastTxRequest{ + Mode: sdktx.BroadcastMode_BROADCAST_MODE_SYNC, + TxBytes: txBytes, + }, + ) + if err != nil { + return nil, err + } + if resp.TxResponse.Code != abci.CodeTypeOK { + if apperrors.IsNonceMismatchCode(resp.TxResponse.Code) { + // query the account to update the sequence number on-chain for the account + _, seqNum, err := QueryAccount(ctx, s.grpc, s.registry, s.signer.accounts[signer].address) + if err != nil { + return nil, fmt.Errorf("querying account for new sequence number: %w\noriginal tx response: %s", err, resp.TxResponse.RawLog) + } + if err := s.signer.SetSequence(signer, seqNum); err != nil { + return nil, fmt.Errorf("setting sequence: %w", err) + } + return s.retryBroadcastingTx(ctx, txBytes) + } + return resp.TxResponse, fmt.Errorf("tx failed with code %d: %s", resp.TxResponse.Code, resp.TxResponse.RawLog) + } + + // after the transaction has been submitted, we can increment the + // sequence of the signer + if err := s.signer.IncrementSequence(signer); err != nil { + return nil, fmt.Errorf("increment sequencing: %w", err) + } + return resp.TxResponse, nil +} + +// retryBroadcastingTx creates a new transaction by copying over an existing transaction but creates a new signature with the +// new sequence number. It then calls `broadcastTx` and attempts to submit the transaction +func (s *TxClient) retryBroadcastingTx(ctx context.Context, txBytes []byte) (*sdktypes.TxResponse, error) { + blobTx, isBlobTx := blob.UnmarshalBlobTx(txBytes) + if isBlobTx { + txBytes = blobTx.Tx + } + tx, err := s.signer.DecodeTx(txBytes) + if err != nil { + return nil, err + } + txBuilder := s.signer.txBuilder() + if err := txBuilder.SetMsgs(tx.GetMsgs()...); err != nil { + return nil, err + } + if granter := tx.FeeGranter(); granter != nil { + txBuilder.SetFeeGranter(granter) + } + if payer := tx.FeePayer(); payer != nil { + txBuilder.SetFeePayer(payer) + } + if memo := tx.GetMemo(); memo != "" { + txBuilder.SetMemo(memo) + } + if fee := tx.GetFee(); fee != nil { + txBuilder.SetFeeAmount(fee) + } + if gas := tx.GetGas(); gas > 0 { + txBuilder.SetGasLimit(gas) + } + + signer, _, err := s.signer.signTransaction(txBuilder) + if err != nil { + return nil, fmt.Errorf("resigning transaction: %w", err) + } + + newTxBytes, err := s.signer.EncodeTx(txBuilder.GetTx()) + if err != nil { + return nil, err + } + + // rewrap the blob tx if it was originally a blob tx + if isBlobTx { + newTxBytes, err = blob.MarshalBlobTx(newTxBytes, blobTx.Blobs...) + if err != nil { + return nil, err + } + } + + return s.broadcastTx(ctx, newTxBytes, signer) +} + +// ConfirmTx periodically pings the provided node for the commitment of a transaction by its +// hash. It will continually loop until the context is cancelled, the tx is found or an error +// is encountered. +func (s *TxClient) ConfirmTx(ctx context.Context, txHash string) (*sdktypes.TxResponse, error) { + txClient := sdktx.NewServiceClient(s.grpc) + + pollTicker := time.NewTicker(s.pollTime) + defer pollTicker.Stop() + + for { + resp, err := txClient.GetTx(ctx, &sdktx.GetTxRequest{Hash: txHash}) + if err == nil { + if resp.TxResponse.Code != 0 { + return resp.TxResponse, fmt.Errorf("tx was included but failed with code %d: %s", resp.TxResponse.Code, resp.TxResponse.RawLog) + } + return resp.TxResponse, nil + } + // FIXME: this is a relatively brittle of working out whether to retry or not. The tx might be not found for other + // reasons. It may have been removed from the mempool at a later point. We should build an endpoint that gives the + // signer more information on the status of their transaction and then update the logic here + if !strings.Contains(err.Error(), "not found") { + return &sdktypes.TxResponse{}, err + } + + // Wait for the next round. + select { + case <-ctx.Done(): + return &sdktypes.TxResponse{}, ctx.Err() + case <-pollTicker.C: + } + } +} + +func (s *TxClient) EstimateGas(ctx context.Context, msgs []sdktypes.Msg, opts ...TxOption) (uint64, error) { + s.mtx.Lock() + defer s.mtx.Unlock() + + txBuilder := s.signer.txBuilder(opts...) + if err := txBuilder.SetMsgs(msgs...); err != nil { + return 0, err + } + + _, _, err := s.signer.signTransaction(txBuilder) + if err != nil { + return 0, err + } + + txBytes, err := s.signer.EncodeTx(txBuilder.GetTx()) + if err != nil { + return 0, err + } + + resp, err := sdktx.NewServiceClient(s.grpc).Simulate(ctx, &sdktx.SimulateRequest{ + TxBytes: txBytes, + }) + if err != nil { + return 0, err + } + + gasLimit := uint64(float64(resp.GasInfo.GasUsed) * s.gasMultiplier) + return gasLimit, nil +} + +// Account returns an account of the signer from the key name. Also returns a bool if the +// account exists. +// Thread-safe +func (s *TxClient) Account(name string) (*Account, bool) { + s.mtx.Lock() + defer s.mtx.Unlock() + acc, exists := s.signer.accounts[name] + if !exists { + return nil, false + } + return acc.Copy(), true +} + +func (s *TxClient) AccountByAddress(address sdktypes.AccAddress) *Account { + s.mtx.Lock() + defer s.mtx.Unlock() + return s.signer.AccountByAddress(address) +} + +func (s *TxClient) DefaultAddress() sdktypes.AccAddress { + return s.defaultAddress +} + +func (s *TxClient) DefaultAccountName() string { return s.defaultAccount } + +func (s *TxClient) checkAccountLoaded(ctx context.Context, account string) error { + if _, exists := s.signer.accounts[account]; exists { + return nil + } + record, err := s.signer.keys.Key(account) + if err != nil { + return fmt.Errorf("trying to find account %s on keyring: %w", account, err) + } + addr, err := record.GetAddress() + if err != nil { + return fmt.Errorf("retrieving address from keyring: %w", err) + } + accNum, sequence, err := QueryAccount(ctx, s.grpc, s.registry, addr) + if err != nil { + return fmt.Errorf("querying account %s: %w", account, err) + } + return s.signer.AddAccount(NewAccount(account, accNum, sequence)) +} + +func (s *TxClient) getAccountNameFromMsgs(msgs []sdktypes.Msg) (string, error) { + var addr sdktypes.AccAddress + for _, msg := range msgs { + signers := msg.GetSigners() + if len(signers) != 1 { + return "", fmt.Errorf("only one signer per transaction supported, got %d", len(signers)) + } + if addr == nil { + addr = signers[0] + } + if !bytes.Equal(addr, signers[0]) { + return "", errors.New("not supported: got two different signers across multiple messages") + } + } + record, err := s.signer.keys.KeyByAddress(addr) + if err != nil { + return "", err + } + return record.Name, nil +} + +// Signer exposes the tx clients underlying signer +func (s *TxClient) Signer() *Signer { + return s.signer +} diff --git a/pkg/user/signer_test.go b/pkg/user/tx_client_test.go similarity index 60% rename from pkg/user/signer_test.go rename to pkg/user/tx_client_test.go index e01bd48ab9..cf177e888f 100644 --- a/pkg/user/signer_test.go +++ b/pkg/user/tx_client_test.go @@ -2,7 +2,6 @@ package user_test import ( "context" - "fmt" "testing" "time" @@ -20,57 +19,54 @@ import ( "github.com/celestiaorg/celestia-app/v2/test/util/testnode" ) -func TestSignerTestSuite(t *testing.T) { +func TestTxClientTestSuite(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode.") } - suite.Run(t, new(SignerTestSuite)) + suite.Run(t, new(TxClientTestSuite)) } -type SignerTestSuite struct { +type TxClientTestSuite struct { suite.Suite - ctx testnode.Context - encCfg encoding.Config - signer *user.Signer + ctx testnode.Context + encCfg encoding.Config + txClient *user.TxClient } -func (s *SignerTestSuite) SetupSuite() { +func (s *TxClientTestSuite) SetupSuite() { s.encCfg = encoding.MakeConfig(app.ModuleEncodingRegisters...) s.ctx, _, _ = testnode.NewNetwork(s.T(), testnode.DefaultConfig().WithFundedAccounts("a")) _, err := s.ctx.WaitForHeight(1) s.Require().NoError(err) - rec, err := s.ctx.Keyring.Key("a") - s.Require().NoError(err) - addr, err := rec.GetAddress() - s.Require().NoError(err) - s.signer, err = user.SetupSigner(s.ctx.GoContext(), s.ctx.Keyring, s.ctx.GRPCClient, addr, s.encCfg, user.WithGasMultiplier(1.2)) + s.txClient, err = user.SetupTxClient(s.ctx.GoContext(), s.ctx.Keyring, s.ctx.GRPCClient, s.encCfg, user.WithGasMultiplier(1.2)) s.Require().NoError(err) } -func (s *SignerTestSuite) TestSubmitPayForBlob() { +func (s *TxClientTestSuite) TestSubmitPayForBlob() { t := s.T() blobs := blobfactory.ManyRandBlobs(rand.NewRand(), 1e3, 1e4) fee := user.SetFee(1e6) gas := user.SetGasLimit(1e6) subCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - resp, err := s.signer.SubmitPayForBlob(subCtx, blobs, fee, gas) + resp, err := s.txClient.SubmitPayForBlob(subCtx, blobs, fee, gas) require.NoError(t, err) require.EqualValues(t, 0, resp.Code) } -func (s *SignerTestSuite) TestSubmitTx() { +func (s *TxClientTestSuite) TestSubmitTx() { t := s.T() fee := user.SetFee(1e6) gas := user.SetGasLimit(1e6) - msg := bank.NewMsgSend(s.signer.Address(), testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 10))) - resp, err := s.signer.SubmitTx(s.ctx.GoContext(), []sdk.Msg{msg}, fee, gas) + addr := s.txClient.DefaultAddress() + msg := bank.NewMsgSend(addr, testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 10))) + resp, err := s.txClient.SubmitTx(s.ctx.GoContext(), []sdk.Msg{msg}, fee, gas) require.NoError(t, err) require.EqualValues(t, 0, resp.Code) } -func (s *SignerTestSuite) TestConfirmTx() { +func (s *TxClientTestSuite) TestConfirmTx() { t := s.T() fee := user.SetFee(1e6) @@ -79,7 +75,7 @@ func (s *SignerTestSuite) TestConfirmTx() { t.Run("deadline exceeded when the context times out", func(t *testing.T) { ctx, cancel := context.WithTimeout(s.ctx.GoContext(), time.Second) defer cancel() - _, err := s.signer.ConfirmTx(ctx, "E32BD15CAF57AF15D17B0D63CF4E63A9835DD1CEBB059C335C79586BC3013728") + _, err := s.txClient.ConfirmTx(ctx, "E32BD15CAF57AF15D17B0D63CF4E63A9835DD1CEBB059C335C79586BC3013728") require.Error(t, err) require.Contains(t, err.Error(), context.DeadlineExceeded.Error()) }) @@ -87,38 +83,41 @@ func (s *SignerTestSuite) TestConfirmTx() { t.Run("should error when tx is not found", func(t *testing.T) { ctx, cancel := context.WithTimeout(s.ctx.GoContext(), 5*time.Second) defer cancel() - _, err := s.signer.ConfirmTx(ctx, "not found tx") + _, err := s.txClient.ConfirmTx(ctx, "not found tx") require.Error(t, err) }) t.Run("should success when tx is found immediately", func(t *testing.T) { - msg := bank.NewMsgSend(s.signer.Address(), testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 10))) - resp, err := s.submitTxWithoutConfirm([]sdk.Msg{msg}, fee, gas) + addr := s.txClient.DefaultAddress() + msg := bank.NewMsgSend(addr, testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 10))) + resp, err := s.txClient.BroadcastTx(s.ctx.GoContext(), []sdk.Msg{msg}, fee, gas) require.NoError(t, err) require.NotNil(t, resp) ctx, cancel := context.WithTimeout(s.ctx.GoContext(), 30*time.Second) defer cancel() - resp, err = s.signer.ConfirmTx(ctx, resp.TxHash) + resp, err = s.txClient.ConfirmTx(ctx, resp.TxHash) require.NoError(t, err) require.Equal(t, abci.CodeTypeOK, resp.Code) }) t.Run("should error when tx is found with a non-zero error code", func(t *testing.T) { balance := s.queryCurrentBalance(t) + addr := s.txClient.DefaultAddress() // Create a msg send with out of balance, ensure this tx fails - msg := bank.NewMsgSend(s.signer.Address(), testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 1+balance))) - resp, err := s.submitTxWithoutConfirm([]sdk.Msg{msg}, fee, gas) + msg := bank.NewMsgSend(addr, testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 1+balance))) + resp, err := s.txClient.BroadcastTx(s.ctx.GoContext(), []sdk.Msg{msg}, fee, gas) require.NoError(t, err) require.NotNil(t, resp) - resp, err = s.signer.ConfirmTx(s.ctx.GoContext(), resp.TxHash) + resp, err = s.txClient.ConfirmTx(s.ctx.GoContext(), resp.TxHash) require.Error(t, err) require.NotEqual(t, abci.CodeTypeOK, resp.Code) }) } -func (s *SignerTestSuite) TestGasEstimation() { - msg := bank.NewMsgSend(s.signer.Address(), testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 10))) - gas, err := s.signer.EstimateGas(s.ctx.GoContext(), []sdk.Msg{msg}) +func (s *TxClientTestSuite) TestGasEstimation() { + addr := s.txClient.DefaultAddress() + msg := bank.NewMsgSend(addr, testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 10))) + gas, err := s.txClient.EstimateGas(s.ctx.GoContext(), []sdk.Msg{msg}) require.NoError(s.T(), err) require.Greater(s.T(), gas, uint64(0)) } @@ -127,11 +126,12 @@ func (s *SignerTestSuite) TestGasEstimation() { // based on the fee provided in the tx instead of the gas used by the tx. This // behavior leads to poor UX because tx submitters must over-estimate the amount // of gas that their tx will consume and they are not refunded for the excess. -func (s *SignerTestSuite) TestGasConsumption() { +func (s *TxClientTestSuite) TestGasConsumption() { t := s.T() utiaToSend := int64(1) - msg := bank.NewMsgSend(s.signer.Address(), testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, utiaToSend))) + addr := s.txClient.DefaultAddress() + msg := bank.NewMsgSend(addr, testnode.RandomAddress().(sdk.AccAddress), sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, utiaToSend))) gasPrice := int64(1) gasLimit := uint64(1e6) @@ -141,7 +141,7 @@ func (s *SignerTestSuite) TestGasConsumption() { options := []user.TxOption{user.SetGasLimit(gasLimit), user.SetFee(fee)} balanceBefore := s.queryCurrentBalance(t) - resp, err := s.signer.SubmitTx(s.ctx.GoContext(), []sdk.Msg{msg}, options...) + resp, err := s.txClient.SubmitTx(s.ctx.GoContext(), []sdk.Msg{msg}, options...) require.NoError(t, err) require.EqualValues(t, abci.CodeTypeOK, resp.Code) @@ -158,25 +158,10 @@ func (s *SignerTestSuite) TestGasConsumption() { require.Less(t, gasUsedBasedDeduction, int64(fee)) } -func (s *SignerTestSuite) queryCurrentBalance(t *testing.T) int64 { +func (s *TxClientTestSuite) queryCurrentBalance(t *testing.T) int64 { balanceQuery := bank.NewQueryClient(s.ctx.GRPCClient) - balanceResp, err := balanceQuery.AllBalances(s.ctx.GoContext(), &bank.QueryAllBalancesRequest{Address: s.signer.Address().String()}) + addr := s.txClient.DefaultAddress() + balanceResp, err := balanceQuery.AllBalances(s.ctx.GoContext(), &bank.QueryAllBalancesRequest{Address: addr.String()}) require.NoError(t, err) return balanceResp.Balances.AmountOf(app.BondDenom).Int64() } - -func (s *SignerTestSuite) submitTxWithoutConfirm(msgs []sdk.Msg, opts ...user.TxOption) (*sdk.TxResponse, error) { - txBytes, err := s.signer.CreateTx(msgs, opts...) - if err != nil { - return nil, err - } - - resp, err := s.signer.BroadcastTx(s.ctx.GoContext(), txBytes) - if err != nil { - return nil, err - } - if resp.Code != 0 { - return resp, fmt.Errorf("tx failed with code %d: %s", resp.Code, resp.RawLog) - } - return resp, nil -} diff --git a/test/txsim/account.go b/test/txsim/account.go index 44e777e22c..e9c4b2042b 100644 --- a/test/txsim/account.go +++ b/test/txsim/account.go @@ -1,7 +1,6 @@ package txsim import ( - "bytes" "context" "errors" "fmt" @@ -36,11 +35,12 @@ type AccountManager struct { // to protect from concurrent writes to the map mtx sync.Mutex - master *user.Signer + txClient *user.TxClient balance uint64 latestHeight uint64 lastUpdated time.Time - subaccounts map[string]*user.Signer + accountIndex int + addressMap map[string]string } func NewAccountManager( @@ -62,13 +62,14 @@ func NewAccountManager( } am := &AccountManager{ - keys: keys, - subaccounts: make(map[string]*user.Signer), - encCfg: encCfg, - pending: make([]*account, 0), - conn: conn, - pollTime: pollTime, - useFeegrant: useFeegrant, + keys: keys, + encCfg: encCfg, + pending: make([]*account, 0), + conn: conn, + pollTime: pollTime, + useFeegrant: useFeegrant, + addressMap: make(map[string]string), + accountIndex: len(records), } if masterAccName == "" { @@ -145,13 +146,13 @@ func (am *AccountManager) setupMasterAccount(ctx context.Context, masterAccName return fmt.Errorf("error getting master account %s balance: %w", masterAccName, err) } - am.master, err = user.SetupSigner(ctx, am.keys, am.conn, masterAddress, am.encCfg) + am.txClient, err = user.SetupTxClient(ctx, am.keys, am.conn, am.encCfg, user.WithDefaultAccount(masterAccName), user.WithPollTime(am.pollTime)) if err != nil { return err } log.Info(). - Str("address", am.master.Address().String()). + Str("address", masterAddress.String()). Uint64("balance", am.balance). Msg("set master account") @@ -171,7 +172,8 @@ func (am *AccountManager) AllocateAccounts(n, balance int) []types.AccAddress { path := hd.CreateHDPath(types.CoinType, 0, 0).String() addresses := make([]types.AccAddress, n) for i := 0; i < n; i++ { - record, _, err := am.keys.NewMnemonic(am.nextAccountName(), keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + name := am.nextAccountName() + record, _, err := am.keys.NewMnemonic(name, keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1) if err != nil { panic(err) } @@ -181,6 +183,7 @@ func (am *AccountManager) AllocateAccounts(n, balance int) []types.AccAddress { } am.pending = append(am.pending, &account{ + keyName: name, address: addresses[i], balance: uint64(balance), }) @@ -207,6 +210,8 @@ func (am *AccountManager) Submit(ctx context.Context, op Operation) error { if address == nil { address = signers[0] + } else if !address.Equals(signers[0]) { + return fmt.Errorf("all messages must be signed by the same account") } } @@ -218,11 +223,6 @@ func (am *AccountManager) Submit(ctx context.Context, op Operation) error { } } - signer, err := am.getSubAccount(address) - if err != nil { - return err - } - opts := make([]user.TxOption, 0) if op.GasLimit == 0 { opts = append(opts, user.SetGasLimit(DefaultGasLimit), user.SetFee(defaultFee)) @@ -236,24 +236,29 @@ func (am *AccountManager) Submit(ctx context.Context, op Operation) error { } if am.useFeegrant { - opts = append(opts, user.SetFeeGranter(am.master.Address())) + opts = append(opts, user.SetFeeGranter(am.txClient.DefaultAddress())) } - var res *types.TxResponse - var size int64 + var ( + res *types.TxResponse + err error + ) if len(op.Blobs) > 0 { - size = getSize(op.Blobs) - res, err = signer.SubmitPayForBlob(ctx, op.Blobs, opts...) + accName, ok := am.addressMap[address.String()] + if !ok { + return fmt.Errorf("account not found for address %s", address.String()) + } + res, err = am.txClient.SubmitPayForBlobsWithAccount(ctx, accName, op.Blobs, opts...) if err != nil { // log the failed tx log.Err(err). Str("address", address.String()). Str("blobs count", fmt.Sprintf("%d", len(op.Blobs))). - Int64("total byte size of blobs", size). + Int64("total byte size of blobs", getSize(op.Blobs)). Msg("tx failed") } } else { - res, err = signer.SubmitTx(ctx, op.Msgs, opts...) + res, err = am.txClient.SubmitTx(ctx, op.Msgs, opts...) // log the failed tx if err != nil { log.Err(err). @@ -274,7 +279,7 @@ func (am *AccountManager) Submit(ctx context.Context, op Operation) error { Int64("height", res.Height). Str("address", address.String()). Str("blobs count", fmt.Sprintf("%d", len(op.Blobs))). - Int64("total byte size of blobs", size). + Int64("total byte size of blobs", getSize(op.Blobs)). Msg("tx committed") } else { log.Info(). @@ -312,7 +317,7 @@ func (am *AccountManager) GenerateAccounts(ctx context.Context) error { if am.useFeegrant { // create a feegrant message so that the master account pays for all the fees of the sub accounts - feegrantMsg, err := feegrant.NewMsgGrantAllowance(&feegrant.BasicAllowance{}, am.master.Address(), acc.address) + feegrantMsg, err := feegrant.NewMsgGrantAllowance(&feegrant.BasicAllowance{}, am.txClient.DefaultAddress(), acc.address) if err != nil { return fmt.Errorf("error creating feegrant message: %w", err) } @@ -320,7 +325,7 @@ func (am *AccountManager) GenerateAccounts(ctx context.Context) error { gasLimit += FeegrantGasLimit } - bankMsg := bank.NewMsgSend(am.master.Address(), acc.address, types.NewCoins(types.NewInt64Coin(appconsts.BondDenom, int64(acc.balance)))) + bankMsg := bank.NewMsgSend(am.txClient.DefaultAddress(), acc.address, types.NewCoins(types.NewInt64Coin(appconsts.BondDenom, int64(acc.balance)))) msgs = append(msgs, bankMsg) gasLimit += SendGasLimit } @@ -330,23 +335,13 @@ func (am *AccountManager) GenerateAccounts(ctx context.Context) error { return fmt.Errorf("error funding accounts: %w", err) } - // check that the account now exists + // print the new accounts for _, acc := range am.pending { - signer, err := user.SetupSigner(ctx, am.keys, am.conn, acc.address, am.encCfg) - if err != nil { - return err - } - - signer.SetPollTime(am.pollTime) - - // set the account - am.mtx.Lock() - am.subaccounts[acc.address.String()] = signer - am.mtx.Unlock() + am.accountIndex++ + am.addressMap[acc.address.String()] = acc.keyName log.Info(). Str("address", acc.address.String()). Uint64("balance", acc.balance). - Uint64("account number", signer.AccountNumber()). Msg("initialized account") } @@ -367,19 +362,6 @@ func (am *AccountManager) getBalance(ctx context.Context, address types.AccAddre return balanceResp.GetBalance().Amount.Uint64(), nil } -func (am *AccountManager) getSubAccount(address types.AccAddress) (*user.Signer, error) { - am.mtx.Lock() - defer am.mtx.Unlock() - signer, exists := am.subaccounts[address.String()] - if !exists { - if bytes.Equal(am.master.Address(), address) { - return am.master, nil - } - return nil, fmt.Errorf("account %s does not exist", address) - } - return signer, nil -} - func (am *AccountManager) waitDelay(ctx context.Context, blocks uint64) error { latestHeight, err := am.updateHeight(ctx) if err != nil { @@ -429,10 +411,11 @@ func (am *AccountManager) updateHeight(ctx context.Context) (uint64, error) { func (am *AccountManager) nextAccountName() string { am.mtx.Lock() defer am.mtx.Unlock() - return accountName(len(am.pending) + len(am.subaccounts)) + return accountName(len(am.pending) + am.accountIndex) } type account struct { + keyName string address types.AccAddress balance uint64 } diff --git a/test/util/blobfactory/payforblob_factory.go b/test/util/blobfactory/payforblob_factory.go index 322360937d..9860999940 100644 --- a/test/util/blobfactory/payforblob_factory.go +++ b/test/util/blobfactory/payforblob_factory.go @@ -83,7 +83,6 @@ func RandMsgPayForBlobs(rand *tmrand.Rand, size int) (*blobtypes.MsgPayForBlobs, } func RandBlobTxsRandomlySized(signer *user.Signer, rand *tmrand.Rand, count, maxSize, maxBlobs int) coretypes.Txs { - addr := signer.Address() opts := DefaultTxOpts() txs := make([]coretypes.Tx, count) for i := 0; i < count; i++ { @@ -96,8 +95,8 @@ func RandBlobTxsRandomlySized(signer *user.Signer, rand *tmrand.Rand, count, max if blobCount == 0 { blobCount = 1 } - _, blobs := RandMsgPayForBlobsWithSigner(rand, addr.String(), size, blobCount) - cTx, err := signer.CreatePayForBlob(blobs, opts...) + _, blobs := RandMsgPayForBlobsWithSigner(rand, testfactory.TestAccName, size, blobCount) + cTx, _, err := signer.CreatePayForBlobs(testfactory.TestAccName, blobs, opts...) if err != nil { panic(err) } @@ -135,7 +134,7 @@ func RandBlobTxsWithAccounts( txs := make([]coretypes.Tx, len(accounts)) for i := 0; i < len(accounts); i++ { addr := testfactory.GetAddress(kr, accounts[i]) - signer, err := user.SetupSigner(context.Background(), kr, conn, addr, enc) + client, err := user.SetupTxClient(context.Background(), kr, conn, enc) if err != nil { panic(err) } @@ -156,7 +155,7 @@ func RandBlobTxsWithAccounts( } _, blobs := RandMsgPayForBlobsWithSigner(rand, addr.String(), randomizedSize, randomizedBlobCount) - cTx, err := signer.CreatePayForBlob(blobs, opts...) + cTx, _, err := client.Signer().CreatePayForBlobs(accounts[i], blobs, opts...) if err != nil { panic(err) } @@ -167,10 +166,11 @@ func RandBlobTxsWithAccounts( } func RandBlobTxs(signer *user.Signer, rand *tmrand.Rand, count, blobsPerTx, size int) coretypes.Txs { + addr := signer.Account(testfactory.TestAccName).Address() txs := make([]coretypes.Tx, count) for i := 0; i < count; i++ { - _, blobs := RandMsgPayForBlobsWithSigner(rand, signer.Address().String(), size, blobsPerTx) - tx, err := signer.CreatePayForBlob(blobs, DefaultTxOpts()...) + _, blobs := RandMsgPayForBlobsWithSigner(rand, addr.String(), size, blobsPerTx) + tx, _, err := signer.CreatePayForBlobs(testfactory.TestAccName, blobs, DefaultTxOpts()...) if err != nil { panic(err) } @@ -227,10 +227,9 @@ func ManyMultiBlobTx( txs := make([][]byte, len(accounts)) opts := DefaultTxOpts() for i, acc := range accounts { - addr := testfactory.GetAddress(kr, acc) - signer, err := user.NewSigner(kr, nil, addr, enc, chainid, accInfos[i].AccountNum, accInfos[i].Sequence, appconsts.LatestVersion) + signer, err := user.NewSigner(kr, enc, chainid, appconsts.LatestVersion, user.NewAccount(acc, accInfos[i].AccountNum, accInfos[i].Sequence)) require.NoError(t, err) - txs[i], err = signer.CreatePayForBlob(blobs[i], opts...) + txs[i], _, err = signer.CreatePayForBlobs(acc, blobs[i], opts...) require.NoError(t, err) } return txs @@ -245,16 +244,16 @@ func IndexWrappedTxWithInvalidNamespace( index uint32, ) (coretypes.Tx, *blob.Blob) { t.Helper() - addr := signer.Address() blob := ManyRandBlobs(rand, 100)[0] - msg, err := blobtypes.NewMsgPayForBlobs(addr.String(), appconsts.LatestVersion, blob) + acc := signer.Accounts()[0] + require.NotNil(t, acc) + msg, err := blobtypes.NewMsgPayForBlobs(acc.Address().String(), appconsts.LatestVersion, blob) require.NoError(t, err) msg.Namespaces[0] = bytes.Repeat([]byte{1}, 33) // invalid namespace - tx, err := signer.CreateTx([]sdk.Msg{msg}, DefaultTxOpts()...) + rawTx, err := signer.CreateTx([]sdk.Msg{msg}, DefaultTxOpts()...) require.NoError(t, err) - rawTx, err := signer.EncodeTx(tx) require.NoError(t, err) cTx, err := coretypes.MarshalIndexWrapper(rawTx, index) @@ -268,12 +267,12 @@ func RandBlobTxsWithNamespacesAndSigner( namespaces []appns.Namespace, sizes []int, ) []coretypes.Tx { - addr := signer.Address() txs := make([]coretypes.Tx, len(namespaces)) for i := 0; i < len(namespaces); i++ { - // TODO: this can be refactored as the signer only needs the blobs and can construct the PFB itself - _, b := RandMsgPayForBlobsWithNamespaceAndSigner(addr.String(), namespaces[i], sizes[i]) - cTx, err := signer.CreatePayForBlob([]*blob.Blob{b}, DefaultTxOpts()...) + // take the first account the signer has + acc := signer.Accounts()[0] + _, b := RandMsgPayForBlobsWithNamespaceAndSigner(acc.Address().String(), namespaces[i], sizes[i]) + cTx, _, err := signer.CreatePayForBlobs(acc.Name(), []*blob.Blob{b}, DefaultTxOpts()...) if err != nil { panic(err) } @@ -285,13 +284,14 @@ func RandBlobTxsWithNamespacesAndSigner( func ComplexBlobTxWithOtherMsgs(t *testing.T, rand *tmrand.Rand, signer *user.Signer, msgs ...sdk.Msg) coretypes.Tx { t.Helper() - pfb, blobs := RandMsgPayForBlobsWithSigner(rand, signer.Address().String(), 100, 1) + addr := signer.Accounts()[0].Address().String() + + pfb, blobs := RandMsgPayForBlobsWithSigner(rand, addr, 100, 1) msgs = append(msgs, pfb) - tx, err := signer.CreateTx(msgs, DefaultTxOpts()...) + rawTx, err := signer.CreateTx(msgs, DefaultTxOpts()...) require.NoError(t, err) - rawTx, err := signer.EncodeTx(tx) require.NoError(t, err) btx, err := blob.MarshalBlobTx(rawTx, blobs...) @@ -332,7 +332,7 @@ func RandMultiBlobTxsSameSigner(t *testing.T, rand *tmrand.Rand, signer *user.Si blobsPerPfb := GenerateRandomBlobCount(rand) blobSizes := GenerateRandomBlobSizes(blobsPerPfb, rand) blobs := ManyRandBlobs(rand, blobSizes...) - pfbTxs[i], err = signer.CreatePayForBlob(blobs) + pfbTxs[i], _, err = signer.CreatePayForBlobs(testfactory.TestAccName, blobs) require.NoError(t, err) } return pfbTxs diff --git a/test/util/blobfactory/payforblob_factory_test.go b/test/util/blobfactory/payforblob_factory_test.go index 988b329034..4e2955755f 100644 --- a/test/util/blobfactory/payforblob_factory_test.go +++ b/test/util/blobfactory/payforblob_factory_test.go @@ -6,6 +6,7 @@ import ( "github.com/celestiaorg/celestia-app/v2/app" "github.com/celestiaorg/celestia-app/v2/app/encoding" "github.com/celestiaorg/celestia-app/v2/test/util/blobfactory" + "github.com/celestiaorg/celestia-app/v2/test/util/testfactory" "github.com/celestiaorg/celestia-app/v2/test/util/testnode" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -25,7 +26,7 @@ func TestRandMultiBlobTxsSameSigner_Deterministic(t *testing.T) { rand1.Seed(1) marshalledBlobTxs1 := blobfactory.RandMultiBlobTxsSameSigner(t, rand1, signer, pfbCount) - signer.ForceSetSequence(0) + require.NoError(t, signer.SetSequence(testfactory.TestAccName, 0)) rand2 := tmrand.NewRand() rand2.Seed(1) marshalledBlobTxs2 := blobfactory.RandMultiBlobTxsSameSigner(t, rand2, signer, pfbCount) diff --git a/test/util/blobfactory/test_util.go b/test/util/blobfactory/test_util.go index 3bd049e96f..868d5dcbce 100644 --- a/test/util/blobfactory/test_util.go +++ b/test/util/blobfactory/test_util.go @@ -3,6 +3,7 @@ package blobfactory import ( "github.com/celestiaorg/celestia-app/v2/pkg/appconsts" "github.com/celestiaorg/celestia-app/v2/pkg/user" + "github.com/celestiaorg/celestia-app/v2/test/util/testfactory" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" tmrand "github.com/tendermint/tendermint/libs/rand" @@ -41,7 +42,7 @@ func GenerateRawSendTx(signer *user.Signer, amount int64) []byte { Amount: sdk.NewInt(amount), } - addr := signer.Address() + addr := signer.Account(testfactory.TestAccName).Address() msg := banktypes.NewMsgSend(addr, addr, sdk.NewCoins(amountCoin)) tx, err := signer.CreateTx([]sdk.Msg{msg}, opts...) @@ -49,11 +50,7 @@ func GenerateRawSendTx(signer *user.Signer, amount int64) []byte { panic(err) } - rawTx, err := signer.EncodeTx(tx) - if err != nil { - panic(err) - } - return rawTx + return tx } // GenerateRandomAmount generates a random amount for a Send transaction. diff --git a/test/util/blobfactory/test_util_test.go b/test/util/blobfactory/test_util_test.go index b70e1db142..df8e864378 100644 --- a/test/util/blobfactory/test_util_test.go +++ b/test/util/blobfactory/test_util_test.go @@ -21,15 +21,15 @@ func TestGenerateManyRandomRawSendTxsSameSigner_Deterministic(t *testing.T) { encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) TxDecoder := encCfg.TxConfig.TxDecoder() - kr, addr := testnode.NewKeyring(testfactory.TestAccName) - signer, err := user.NewSigner(kr, nil, addr[0], encCfg.TxConfig, testfactory.ChainID, 1, 0, appconsts.LatestVersion) + kr, _ := testnode.NewKeyring(testfactory.TestAccName) + signer, err := user.NewSigner(kr, encCfg.TxConfig, testfactory.ChainID, appconsts.LatestVersion, user.NewAccount(testfactory.TestAccName, 1, 0)) require.NoError(t, err) rand := tmrand.NewRand() rand.Seed(1) encodedTxs1 := blobfactory.GenerateManyRandomRawSendTxsSameSigner(rand, signer, normalTxCount) - signer.ForceSetSequence(0) + require.NoError(t, signer.SetSequence(testfactory.TestAccName, 0)) rand2 := tmrand.NewRand() rand2.Seed(1) encodedTxs2 := blobfactory.GenerateManyRandomRawSendTxsSameSigner(rand2, signer, normalTxCount) diff --git a/test/util/direct_tx_gen.go b/test/util/direct_tx_gen.go index ade2d21139..ef0b47c24b 100644 --- a/test/util/direct_tx_gen.go +++ b/test/util/direct_tx_gen.go @@ -47,7 +47,8 @@ func RandBlobTxsWithAccounts( for i := 0; i < len(accounts); i++ { addr := testfactory.GetAddress(kr, accounts[i]) acc := DirectQueryAccount(capp, addr) - signer, err := user.NewSigner(kr, nil, addr, cfg, chainid, acc.GetAccountNumber(), acc.GetSequence(), appconsts.LatestVersion) + account := user.NewAccount(accounts[i], acc.GetAccountNumber(), acc.GetSequence()) + signer, err := user.NewSigner(kr, cfg, chainid, capp.AppVersion(), account) require.NoError(t, err) randomizedSize := size @@ -66,7 +67,7 @@ func RandBlobTxsWithAccounts( } _, blobs := blobfactory.RandMsgPayForBlobsWithSigner(tmrand.NewRand(), addr.String(), randomizedSize, randomizedBlobCount) - tx, err := signer.CreatePayForBlob(blobs, opts...) + tx, _, err := signer.CreatePayForBlobs(account.Name(), blobs, opts...) require.NoError(t, err) txs[i] = tx } @@ -102,7 +103,8 @@ func RandBlobTxsWithManualSequence( txs := make([]coretypes.Tx, len(accounts)) for i := 0; i < len(accounts); i++ { addr := testfactory.GetAddress(kr, accounts[i]) - signer, err := user.NewSigner(kr, nil, addr, cfg, chainid, accountNum, sequence, appconsts.LatestVersion) + acc := user.NewAccount(accounts[i], accountNum, sequence) + signer, err := user.NewSigner(kr, cfg, chainid, appconsts.LatestVersion, acc) require.NoError(t, err) randomizedSize := size @@ -131,22 +133,20 @@ func RandBlobTxsWithManualSequence( } require.NoError(t, builder.SetMsgs(msg)) err := builder.SetSignatures(signing.SignatureV2{ - PubKey: signer.PubKey(), + PubKey: acc.PubKey(), Data: &signing.SingleSignatureData{ SignMode: signing.SignMode_SIGN_MODE_DIRECT, Signature: []byte("invalid signature"), }, - Sequence: signer.LocalSequence(), + Sequence: acc.Sequence(), }) require.NoError(t, err) - tx = builder.GetTx() + tx, err = signer.EncodeTx(builder.GetTx()) require.NoError(t, err) } - rawTx, err := signer.EncodeTx(tx) - require.NoError(t, err) - cTx, err := blob.MarshalBlobTx(rawTx, blobs...) + cTx, err := blob.MarshalBlobTx(tx, blobs...) if err != nil { panic(err) } @@ -212,15 +212,13 @@ func SendTxWithManualSequence( opts ...user.TxOption, ) coretypes.Tx { fromAddr, toAddr := getAddress(fromAcc, kr), getAddress(toAcc, kr) - signer, err := user.NewSigner(kr, nil, fromAddr, cfg, chainid, accountNum, sequence, appconsts.LatestVersion) + signer, err := user.NewSigner(kr, cfg, chainid, appconsts.LatestVersion, user.NewAccount(fromAcc, accountNum, sequence)) require.NoError(t, err) msg := banktypes.NewMsgSend(fromAddr, toAddr, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewIntFromUint64(amount)))) - stx, err := signer.CreateTx([]sdk.Msg{msg}, opts...) + rawTx, err := signer.CreateTx([]sdk.Msg{msg}, opts...) require.NoError(t, err) - rawTx, err := signer.EncodeTx(stx) - require.NoError(t, err) return rawTx } diff --git a/test/util/malicious/app_test.go b/test/util/malicious/app_test.go index b0d2d8b959..c14ec79df4 100644 --- a/test/util/malicious/app_test.go +++ b/test/util/malicious/app_test.go @@ -78,10 +78,10 @@ func TestMaliciousTestNode(t *testing.T) { // submit a multiblob tx where each blob is using a random namespace. This // will result in the first two blobs being swapped in the square as per the // malicious square builder. - signer, err := testnode.NewSignerFromContext(cctx, accounts[0]) + client, err := testnode.NewTxClientFromContext(cctx) require.NoError(t, err) blobs := blobfactory.ManyRandBlobs(tmrand.NewRand(), 10_000, 10_000, 10_000, 10_000, 10_000, 10_000, 10_000) - txres, err := signer.SubmitPayForBlob(cctx.GoContext(), blobs, blobfactory.DefaultTxOpts()...) + txres, err := client.SubmitPayForBlob(cctx.GoContext(), blobs, blobfactory.DefaultTxOpts()...) require.NoError(t, err) require.Equal(t, abci.CodeTypeOK, txres.Code) diff --git a/test/util/testnode/node_interaction_api.go b/test/util/testnode/node_interaction_api.go index fc79fb68e0..44d997ea5a 100644 --- a/test/util/testnode/node_interaction_api.go +++ b/test/util/testnode/node_interaction_api.go @@ -239,7 +239,7 @@ func (c *Context) PostData(account, broadcastMode string, ns appns.Namespace, bl } // use the key for accounts[i] to create a singer used for a single PFB - signer, err := user.NewSigner(c.Keyring, c.GRPCClient, addr, c.TxConfig, c.ChainID, acc, seq, appconsts.LatestVersion) + signer, err := user.NewSigner(c.Keyring, c.TxConfig, c.ChainID, appconsts.LatestVersion, user.NewAccount(account, acc, seq)) if err != nil { return nil, err } @@ -252,7 +252,7 @@ func (c *Context) PostData(account, broadcastMode string, ns appns.Namespace, bl gas := types.DefaultEstimateGas([]uint32{uint32(len(blobData))}) opts := blobfactory.FeeTxOpts(gas) - blobTx, err := signer.CreatePayForBlob([]*blob.Blob{b}, opts...) + blobTx, _, err := signer.CreatePayForBlobs(account, []*blob.Blob{b}, opts...) if err != nil { return nil, err } diff --git a/test/util/testnode/signer.go b/test/util/testnode/signer.go index 420e6e14e0..b7227792c8 100644 --- a/test/util/testnode/signer.go +++ b/test/util/testnode/signer.go @@ -10,17 +10,11 @@ import ( func NewOfflineSigner() (*user.Signer, error) { encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) - kr, addr := NewKeyring(testfactory.TestAccName) - return user.NewSigner(kr, nil, addr[0], encCfg.TxConfig, testfactory.ChainID, 1, 0, appconsts.LatestVersion) + kr, _ := NewKeyring(testfactory.TestAccName) + return user.NewSigner(kr, encCfg.TxConfig, testfactory.ChainID, appconsts.LatestVersion, user.NewAccount(testfactory.TestAccName, 0, 0)) } -func NewSingleSignerFromContext(ctx Context) (*user.Signer, error) { +func NewTxClientFromContext(ctx Context) (*user.TxClient, error) { encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) - return user.SetupSingleSigner(ctx.GoContext(), ctx.Keyring, ctx.GRPCClient, encCfg) -} - -func NewSignerFromContext(ctx Context, acc string) (*user.Signer, error) { - encCfg := encoding.MakeConfig(app.ModuleEncodingRegisters...) - addr := testfactory.GetAddress(ctx.Keyring, acc) - return user.SetupSigner(ctx.GoContext(), ctx.Keyring, ctx.GRPCClient, addr, encCfg) + return user.SetupTxClient(ctx.GoContext(), ctx.Keyring, ctx.GRPCClient, encCfg) } diff --git a/x/blob/types/blob_tx_test.go b/x/blob/types/blob_tx_test.go index 8b7fe39e48..47e0bfea48 100644 --- a/x/blob/types/blob_tx_test.go +++ b/x/blob/types/blob_tx_test.go @@ -8,6 +8,7 @@ import ( "github.com/celestiaorg/celestia-app/v2/app/encoding" "github.com/celestiaorg/celestia-app/v2/pkg/appconsts" "github.com/celestiaorg/celestia-app/v2/test/util/blobfactory" + "github.com/celestiaorg/celestia-app/v2/test/util/testfactory" "github.com/celestiaorg/celestia-app/v2/test/util/testnode" "github.com/celestiaorg/celestia-app/v2/x/blob/types" "github.com/celestiaorg/go-square/blob" @@ -39,7 +40,9 @@ func TestValidateBlobTx(t *testing.T) { signer, err := testnode.NewOfflineSigner() require.NoError(t, err) ns1 := namespace.MustNewV0(bytes.Repeat([]byte{0x01}, namespace.NamespaceVersionZeroIDSize)) - addr := signer.Address() + acc := signer.Account(testfactory.TestAccName) + require.NotNil(t, acc) + addr := acc.Address() type test struct { name string @@ -127,10 +130,7 @@ func TestValidateBlobTx(t *testing.T) { msg.ShareCommitments[0] = badCommit - tx, err := signer.CreateTx([]sdk.Msg{msg}) - require.NoError(t, err) - - rawTx, err := signer.EncodeTx(tx) + rawTx, err := signer.CreateTx([]sdk.Msg{msg}) require.NoError(t, err) btx := &blob.BlobTx{ @@ -144,9 +144,7 @@ func TestValidateBlobTx(t *testing.T) { { name: "complex transaction with one send and one pfb", getTx: func() *blob.BlobTx { - signerAddr := signer.Address() - - sendMsg := banktypes.NewMsgSend(signerAddr, signerAddr, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(10)))) + sendMsg := banktypes.NewMsgSend(addr, addr, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(10)))) tx := blobfactory.ComplexBlobTxWithOtherMsgs( t, tmrand.NewRand(), @@ -172,11 +170,10 @@ func TestValidateBlobTx(t *testing.T) { { name: "normal transaction with two blobs w/ different namespaces", getTx: func() *blob.BlobTx { - rawBtx, err := signer.CreatePayForBlob( + rawBtx, _, err := signer.CreatePayForBlobs(acc.Name(), blobfactory.RandBlobsWithNamespace( []namespace.Namespace{namespace.RandomBlobNamespace(), namespace.RandomBlobNamespace()}, - []int{100, 100}), - ) + []int{100, 100})) require.NoError(t, err) btx, isBlobTx := blob.UnmarshalBlobTx(rawBtx) require.True(t, isBlobTx) @@ -187,7 +184,7 @@ func TestValidateBlobTx(t *testing.T) { { name: "normal transaction with two large blobs w/ different namespaces", getTx: func() *blob.BlobTx { - rawBtx, err := signer.CreatePayForBlob( + rawBtx, _, err := signer.CreatePayForBlobs(acc.Name(), blobfactory.RandBlobsWithNamespace( []namespace.Namespace{namespace.RandomBlobNamespace(), namespace.RandomBlobNamespace()}, []int{100000, 1000000}), @@ -203,7 +200,7 @@ func TestValidateBlobTx(t *testing.T) { name: "normal transaction with two blobs w/ same namespace", getTx: func() *blob.BlobTx { ns := namespace.RandomBlobNamespace() - rawBtx, err := signer.CreatePayForBlob( + rawBtx, _, err := signer.CreatePayForBlobs(acc.Name(), blobfactory.RandBlobsWithNamespace( []namespace.Namespace{ns, ns}, []int{100, 100}), @@ -226,7 +223,7 @@ func TestValidateBlobTx(t *testing.T) { sizes[i] = 100 namespaces[i] = ns } - rawBtx, err := signer.CreatePayForBlob( + rawBtx, _, err := signer.CreatePayForBlobs(acc.Name(), blobfactory.RandBlobsWithNamespace( namespaces, sizes, diff --git a/x/blob/types/estimate_gas_test.go b/x/blob/types/estimate_gas_test.go index 8705697deb..44d6c450ec 100644 --- a/x/blob/types/estimate_gas_test.go +++ b/x/blob/types/estimate_gas_test.go @@ -40,13 +40,12 @@ func TestPFBGasEstimation(t *testing.T) { t.Run(fmt.Sprintf("case %d", idx), func(t *testing.T) { accnts := testfactory.GenerateAccounts(1) testApp, kr := testutil.SetupTestAppWithGenesisValSet(app.DefaultInitialConsensusParams(), accnts...) - addr := testfactory.GetAddress(kr, accnts[0]) - signer, err := user.NewSigner(kr, nil, addr, encCfg.TxConfig, testutil.ChainID, 1, 0, appconsts.LatestVersion) + signer, err := user.NewSigner(kr, encCfg.TxConfig, testutil.ChainID, appconsts.LatestVersion, user.NewAccount(accnts[0], 1, 0)) require.NoError(t, err) blobs := blobfactory.ManyRandBlobs(rand, tc.blobSizes...) gas := blobtypes.DefaultEstimateGas(toUint32(tc.blobSizes)) fee := sdk.NewCoins(sdk.NewCoin(app.BondDenom, math.NewInt(int64(gas)))) - tx, err := signer.CreatePayForBlob(blobs, user.SetGasLimit(gas), user.SetFeeAmount(fee)) + tx, _, err := signer.CreatePayForBlobs(accnts[0], blobs, user.SetGasLimit(gas), user.SetFeeAmount(fee)) require.NoError(t, err) blobTx, ok := blob.UnmarshalBlobTx(tx) require.True(t, ok) @@ -85,13 +84,12 @@ func FuzzPFBGasEstimation(f *testing.F) { accnts := testfactory.GenerateAccounts(1) testApp, kr := testutil.SetupTestAppWithGenesisValSet(app.DefaultConsensusParams(), accnts...) - addr := testfactory.GetAddress(kr, accnts[0]) - signer, err := user.NewSigner(kr, nil, addr, encCfg.TxConfig, testutil.ChainID, 1, 0, appconsts.LatestVersion) + signer, err := user.NewSigner(kr, encCfg.TxConfig, testutil.ChainID, appconsts.LatestVersion, user.NewAccount(accnts[0], 1, 0)) require.NoError(t, err) blobs := blobfactory.ManyRandBlobs(rand, blobSizes...) gas := blobtypes.DefaultEstimateGas(toUint32(blobSizes)) fee := sdk.NewCoins(sdk.NewCoin(app.BondDenom, math.NewInt(int64(gas)))) - tx, err := signer.CreatePayForBlob(blobs, user.SetGasLimit(gas), user.SetFeeAmount(fee)) + tx, _, err := signer.CreatePayForBlobs(accnts[0], blobs, user.SetGasLimit(gas), user.SetFeeAmount(fee)) require.NoError(t, err) blobTx, ok := blob.UnmarshalBlobTx(tx) require.True(t, ok) diff --git a/x/blob/types/payforblob.go b/x/blob/types/payforblob.go index 2077662de1..2ea0e07834 100644 --- a/x/blob/types/payforblob.go +++ b/x/blob/types/payforblob.go @@ -52,7 +52,7 @@ func NewMsgPayForBlobs(signer string, version uint64, blobs ...*blob.Blob) (*Msg } commitments, err := inclusion.CreateCommitments(blobs, merkle.HashFromByteSlices, appconsts.SubtreeRootThreshold(version)) if err != nil { - return nil, err + return nil, fmt.Errorf("creating commitments: %w", err) } namespaceVersions, namespaceIDs, sizes, shareVersions := ExtractBlobComponents(blobs) diff --git a/x/blob/types/payforblob_test.go b/x/blob/types/payforblob_test.go index 10911ce60f..a665c3da53 100644 --- a/x/blob/types/payforblob_test.go +++ b/x/blob/types/payforblob_test.go @@ -180,7 +180,7 @@ func validMsgPayForBlobs(t *testing.T) *types.MsgPayForBlobs { ShareVersion: uint32(appconsts.ShareVersionZero), } - addr := signer.Address() + addr := signer.Account(testfactory.TestAccName).Address() pfb, err := types.NewMsgPayForBlobs(addr.String(), appconsts.LatestVersion, pblob) assert.NoError(t, err) @@ -219,7 +219,7 @@ func invalidNamespaceVersionMsgPayForBlobs(t *testing.T) *types.MsgPayForBlobs { namespacesBytes[idx] = namespace.Bytes() } - addr := signer.Address() + addr := signer.Account(testfactory.TestAccName).Address() pfb := &types.MsgPayForBlobs{ Signer: addr.String(), Namespaces: namespacesBytes, diff --git a/x/blobstream/integration_test.go b/x/blobstream/integration_test.go index d49e2728b7..c30d1ed439 100644 --- a/x/blobstream/integration_test.go +++ b/x/blobstream/integration_test.go @@ -80,10 +80,10 @@ func (s *BlobstreamIntegrationSuite) TestBlobstream() { // sign and submit the transactions for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - msgs, addr := tt.msgFunc() - signer, err := user.SetupSigner(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, addr, s.ecfg) + msgs, _ := tt.msgFunc() + txClient, err := user.SetupTxClient(s.cctx.GoContext(), s.cctx.Keyring, s.cctx.GRPCClient, s.ecfg) require.NoError(t, err) - res, err := signer.SubmitTx(s.cctx.GoContext(), msgs, blobfactory.DefaultTxOpts()...) + res, err := txClient.SubmitTx(s.cctx.GoContext(), msgs, blobfactory.DefaultTxOpts()...) if tt.expectedTxCode == abci.CodeTypeOK { require.NoError(t, err) } else { diff --git a/x/signal/legacy_test.go b/x/signal/legacy_test.go index 36642cde2d..84c70a554c 100644 --- a/x/signal/legacy_test.go +++ b/x/signal/legacy_test.go @@ -120,7 +120,7 @@ func (s *LegacyUpgradeTestSuite) TestLegacyGovUpgradeFailure() { require.NoError(t, err) // submit the transaction and wait a block for it to be included - signer, err := testnode.NewSignerFromContext(s.cctx, acc) + signer, err := testnode.NewTxClientFromContext(s.cctx) require.NoError(t, err) subCtx, cancel := context.WithTimeout(s.cctx.GoContext(), time.Minute) defer cancel() @@ -149,7 +149,7 @@ func (s *LegacyUpgradeTestSuite) TestNewGovUpgradeFailure() { require.NoError(t, err) // submit the transaction and wait a block for it to be included - signer, err := testnode.NewSignerFromContext(s.cctx, acc) + signer, err := testnode.NewTxClientFromContext(s.cctx) require.NoError(t, err) subCtx, cancel := context.WithTimeout(s.cctx.GoContext(), time.Minute) defer cancel() @@ -178,7 +178,7 @@ func (s *LegacyUpgradeTestSuite) TestIBCUpgradeFailure() { require.NoError(t, err) // submit the transaction and wait a block for it to be included - signer, err := testnode.NewSignerFromContext(s.cctx, acc) + signer, err := testnode.NewTxClientFromContext(s.cctx) require.NoError(t, err) subCtx, cancel := context.WithTimeout(s.cctx.GoContext(), time.Minute) defer cancel()