diff --git a/node/pkg/chain/helper/helper.go b/node/pkg/chain/helper/helper.go index 6cc74a172..f4faeb1a0 100644 --- a/node/pkg/chain/helper/helper.go +++ b/node/pkg/chain/helper/helper.go @@ -3,12 +3,11 @@ package helper import ( "context" "crypto/ecdsa" - "errors" "math/big" "os" "strings" - "bisonai.com/miko/node/pkg/chain/noncemanager" + "bisonai.com/miko/node/pkg/chain/noncemanagerv2" "bisonai.com/miko/node/pkg/chain/utils" errorSentinel "bisonai.com/miko/node/pkg/error" "bisonai.com/miko/node/pkg/secrets" @@ -60,8 +59,7 @@ func setProviderAndReporter(config *ChainHelperConfig, blockchainType Blockchain func NewChainHelper(ctx context.Context, opts ...ChainHelperOption) (*ChainHelper, error) { config := &ChainHelperConfig{ - BlockchainType: Kaia, - UseAdditionalProviderUrls: true, + BlockchainType: Kaia, } for _, opt := range opts { opt(config) @@ -82,31 +80,11 @@ func NewChainHelper(ctx context.Context, opts ...ChainHelperOption) (*ChainHelpe log.Error().Err(err).Msg("failed to get chain id based on:" + config.ProviderUrl) return nil, err } - clients := make([]utils.ClientInterface, 0) - clients = append(clients, primaryClient) - - if config.UseAdditionalProviderUrls { - providerUrls, providerUrlLoadErr := utils.LoadProviderUrls(ctx, int(chainID.Int64())) - if providerUrlLoadErr != nil { - log.Warn().Err(providerUrlLoadErr).Msg("failed to load additional provider urls") - } - - for _, url := range providerUrls { - subClient, subClientErr := dialFuncs[config.BlockchainType](url) - if subClientErr != nil { - log.Error().Err(subClientErr).Msg("failed to dial sub client") - continue - } - clients = append(clients, subClient) - } - } wallet := strings.TrimPrefix(config.ReporterPk, "0x") - nonce, err := utils.GetNonceFromPk(ctx, wallet, primaryClient) - if err != nil { - return nil, err - } - noncemanager.Set(wallet, nonce) + + nonceManager := noncemanagerv2.New(primaryClient) + go nonceManager.StartAutoRefill(ctx) delegatorUrl := os.Getenv(EnvDelegatorUrl) if delegatorUrl == "" { @@ -114,17 +92,16 @@ func NewChainHelper(ctx context.Context, opts ...ChainHelperOption) (*ChainHelpe } return &ChainHelper{ - clients: clients, + client: primaryClient, wallet: wallet, chainID: chainID, delegatorUrl: delegatorUrl, + noncemanager: nonceManager, }, nil } func (t *ChainHelper) Close() { - for _, helperClient := range t.clients { - helperClient.Close() - } + t.client.Close() } func (t *ChainHelper) GetSignedFromDelegator(tx *types.Transaction) (*types.Transaction, error) { @@ -154,39 +131,20 @@ func (t *ChainHelper) GetSignedFromDelegator(tx *types.Transaction) (*types.Tran } func (t *ChainHelper) MakeDirectTx(ctx context.Context, contractAddressHex string, functionString string, args ...interface{}) (*types.Transaction, error) { - var result *types.Transaction - - nonce, err := utils.GetNonceFromPk(ctx, t.wallet, t.clients[0]) + nonce, err := t.noncemanager.GetNonce(ctx, t.wallet) if err != nil { return nil, err } - job := func(c utils.ClientInterface) error { - tmp, err := utils.MakeDirectTx(ctx, c, contractAddressHex, t.wallet, functionString, t.chainID, nonce, args...) - if err == nil { - result = tmp - } - return err - } - err = t.retryOnJsonRpcFailure(ctx, job) - return result, err + return utils.MakeDirectTx(ctx, t.client, contractAddressHex, t.wallet, functionString, t.chainID, nonce, args...) } func (t *ChainHelper) Submit(ctx context.Context, tx *types.Transaction) error { - return utils.SubmitRawTx(ctx, t.clients[0], tx) + return utils.SubmitRawTx(ctx, t.client, tx) } func (t *ChainHelper) MakeFeeDelegatedTx(ctx context.Context, contractAddressHex string, functionString string, nonce uint64, args ...interface{}) (*types.Transaction, error) { - var result *types.Transaction - job := func(c utils.ClientInterface) error { - tmp, err := utils.MakeFeeDelegatedTx(ctx, c, contractAddressHex, t.wallet, functionString, t.chainID, nonce, args...) - if err == nil { - result = tmp - } - return err - } - err := t.retryOnJsonRpcFailure(ctx, job) - return result, err + return utils.MakeFeeDelegatedTx(ctx, t.client, contractAddressHex, t.wallet, functionString, t.chainID, nonce, args...) } // SignTxByFeePayer: used for testing purpose @@ -195,26 +153,13 @@ func (t *ChainHelper) SignTxByFeePayer(ctx context.Context, tx *types.Transactio } func (t *ChainHelper) ReadContract(ctx context.Context, contractAddressHex string, functionString string, args ...interface{}) (interface{}, error) { - var result interface{} - job := func(c utils.ClientInterface) error { - tmp, err := utils.ReadContract(ctx, c, functionString, contractAddressHex, args...) - if err == nil { - result = tmp - } - return err - } - err := t.retryOnJsonRpcFailure(ctx, job) - return result, err + return utils.ReadContract(ctx, t.client, functionString, contractAddressHex, args...) } func (t *ChainHelper) ChainID() *big.Int { return t.chainID } -func (t *ChainHelper) NumClients() int { - return len(t.clients) -} - func (t *ChainHelper) PublicAddress() (common.Address, error) { // should get the public address of next reporter yet not move the index result := common.Address{} @@ -242,98 +187,34 @@ func (t *ChainHelper) PublicAddressString() (string, error) { return address.Hex(), nil } -func (t *ChainHelper) SubmitDelegatedFallbackDirect(ctx context.Context, contractAddress string, functionString string, maxRetrial int, args ...interface{}) error { - var err error - var tx *types.Transaction - - clientIndex := 0 - - nonce, err := noncemanager.GetAndIncrementNonce(t.wallet) +func (t *ChainHelper) SubmitDelegatedFallbackDirect(ctx context.Context, contractAddress, functionString string, args ...interface{}) error { + nonce, err := t.noncemanager.GetNonce(ctx, t.wallet) if err != nil { return err } - if t.delegatorUrl != "" { - for i := 0; i < maxRetrial; i++ { - tx, err = utils.MakeFeeDelegatedTx(ctx, t.clients[clientIndex], contractAddress, t.wallet, functionString, t.chainID, nonce, args...) - if err != nil { - if utils.ShouldRetryWithSwitchedJsonRPC(err) { - clientIndex = (clientIndex + 1) % len(t.clients) - } - continue - } - - tx, err = t.GetSignedFromDelegator(tx) - if err != nil { - break // if delegator signing fails, try direct transaction - } - - err = utils.SubmitRawTx(ctx, t.clients[clientIndex], tx) - if err != nil { - if utils.ShouldRetryWithSwitchedJsonRPC(err) { - clientIndex = (clientIndex + 1) % len(t.clients) - } else if errors.Is(err, errorSentinel.ErrChainTransactionFail) { - return err // if transaction fails, the data will probably be too old to retry - } else if utils.IsNonceError(err) || err == context.DeadlineExceeded { - err = noncemanager.ResetNonce(ctx, t.wallet, t.clients[clientIndex]) - if err != nil { - return err - } - nonce, err = noncemanager.GetAndIncrementNonce(t.wallet) - if err != nil { - return err - } - } - continue - } - return nil - } + if t.delegatorUrl == "" { + return errorSentinel.ErrChainDelegatorUrlNotFound } - for i := 0; i < maxRetrial; i++ { - tx, err = utils.MakeDirectTx(ctx, t.clients[clientIndex], contractAddress, t.wallet, functionString, t.chainID, nonce, args...) - if err != nil { - if utils.ShouldRetryWithSwitchedJsonRPC(err) { - clientIndex = (clientIndex + 1) % len(t.clients) - } - continue - } + tx, err := utils.MakeFeeDelegatedTx(ctx, t.client, contractAddress, t.wallet, functionString, t.chainID, nonce, args...) + if err != nil { + return err + } - err = utils.SubmitRawTx(ctx, t.clients[clientIndex], tx) - if err != nil { - if utils.ShouldRetryWithSwitchedJsonRPC(err) { - clientIndex = (clientIndex + 1) % len(t.clients) - } else if errors.Is(err, errorSentinel.ErrChainTransactionFail) { - return err // if transaction fails, the data will probably be too old to retry - } else if utils.IsNonceError(err) || err == context.DeadlineExceeded { - err = noncemanager.ResetNonce(ctx, t.wallet, t.clients[clientIndex]) - if err != nil { - return err - } - nonce, err = noncemanager.GetAndIncrementNonce(t.wallet) - if err != nil { - return err - } - } - continue - } - return nil + tx, err = t.GetSignedFromDelegator(tx) + if err != nil { + return t.SubmitDirect(ctx, contractAddress, functionString, args...) } - return err + return utils.SubmitRawTx(ctx, t.client, tx) } -func (t *ChainHelper) retryOnJsonRpcFailure(ctx context.Context, job func(c utils.ClientInterface) error) error { - for _, client := range t.clients { - err := job(client) - if err != nil { - if utils.ShouldRetryWithSwitchedJsonRPC(err) { - log.Error().Err(err).Msg("Error on retrying on JsonRpcFailure") - continue - } - return err - } - break +func (t *ChainHelper) SubmitDirect(ctx context.Context, contractAddress, functionString string, args ...interface{}) error { + tx, err := t.MakeDirectTx(ctx, contractAddress, functionString, args...) + if err != nil { + return err } - return nil + + return utils.SubmitRawTx(ctx, t.client, tx) } diff --git a/node/pkg/chain/helper/signer.go b/node/pkg/chain/helper/signer.go index c1394cab3..9b0aa0dbb 100644 --- a/node/pkg/chain/helper/signer.go +++ b/node/pkg/chain/helper/signer.go @@ -240,6 +240,5 @@ func (s *Signer) Renew(ctx context.Context, newPK *ecdsa.PrivateKey, newPkHex st } func (s *Signer) signerUpdate(ctx context.Context, newAddr common.Address) error { - return s.chainHelper.SubmitDelegatedFallbackDirect(ctx, s.submissionProxyContractAddr, UpdateSignerFuncSignature, maxTxSubmissionRetries, newAddr) - + return s.chainHelper.SubmitDelegatedFallbackDirect(ctx, s.submissionProxyContractAddr, UpdateSignerFuncSignature, newAddr) } diff --git a/node/pkg/chain/helper/types.go b/node/pkg/chain/helper/types.go index c64f7e88e..4d2958cd0 100644 --- a/node/pkg/chain/helper/types.go +++ b/node/pkg/chain/helper/types.go @@ -7,15 +7,17 @@ import ( "time" "bisonai.com/miko/node/pkg/chain/eth_client" + "bisonai.com/miko/node/pkg/chain/noncemanagerv2" "bisonai.com/miko/node/pkg/chain/utils" "github.com/klaytn/klaytn/client" ) type ChainHelper struct { - clients []utils.ClientInterface + client utils.ClientInterface wallet string chainID *big.Int delegatorUrl string + noncemanager *noncemanagerv2.NonceManagerV2 } type ChainHelperConfig struct { @@ -45,12 +47,6 @@ func WithBlockchainType(t BlockchainType) ChainHelperOption { } } -func WithoutAdditionalProviderUrls() ChainHelperOption { - return func(c *ChainHelperConfig) { - c.UseAdditionalProviderUrls = false - } -} - type Signer struct { PK *ecdsa.PrivateKey chainHelper *ChainHelper diff --git a/node/pkg/chain/noncemanagerv2/noncemanagerv2.go b/node/pkg/chain/noncemanagerv2/noncemanagerv2.go new file mode 100644 index 000000000..5efc28902 --- /dev/null +++ b/node/pkg/chain/noncemanagerv2/noncemanagerv2.go @@ -0,0 +1,104 @@ +package noncemanagerv2 + +import ( + "context" + "fmt" + "sync" + "time" + + "bisonai.com/miko/node/pkg/chain/utils" + "github.com/rs/zerolog/log" +) + +type NonceManagerV2 struct { + mu sync.Mutex + noncePool map[string]chan uint64 // address -> nonce pool channel + client utils.ClientInterface +} + +const ( + poolSize = 15 // expect maximum 15 submission per minute + minimumNoncePoolSize = 3 + poolAutoRefillInterval = time.Minute +) + +func New(client utils.ClientInterface) *NonceManagerV2 { + return &NonceManagerV2{ + noncePool: make(map[string]chan uint64), + client: client, + } +} + +func (m *NonceManagerV2) GetNonce(ctx context.Context, address string) (uint64, error) { + m.mu.Lock() + defer m.mu.Unlock() + + if _, ok := m.noncePool[address]; !ok { + log.Debug().Msgf("Initializing nonce pool for address %s", address) + m.noncePool[address] = make(chan uint64, 10) + if err := m.unsafeRefill(ctx, address); err != nil { + return 0, err + } + log.Debug().Msgf("Nonce pool initialized for address %s", address) + } + + if len(m.noncePool[address]) < minimumNoncePoolSize { + log.Debug().Msgf("Low nonce pool size for address %s, refilling...", address) + if err := m.unsafeRefill(ctx, address); err != nil { + return 0, fmt.Errorf("failed to refill nonce pool: %w", err) + } + log.Debug().Msgf("Nonce pool refilled for address %s", address) + } + + nonce := <-m.noncePool[address] + return nonce, nil +} + +func (m *NonceManagerV2) unsafeFlushPool(address string) { + if pool, exists := m.noncePool[address]; exists { + for len(pool) > 0 { + <-pool + } + } else { + m.noncePool[address] = make(chan uint64, 10) + } +} + +func (m *NonceManagerV2) unsafeRefill(ctx context.Context, address string) error { + currentNonce, err := utils.GetNonceFromPk(ctx, address, m.client) + if err != nil { + return err + } + + m.unsafeFlushPool(address) + for i := uint64(0); i < poolSize; i++ { + m.noncePool[address] <- currentNonce + i + } + return nil +} + +func (m *NonceManagerV2) refillAll(ctx context.Context) error { + m.mu.Lock() + defer m.mu.Unlock() + + for address := range m.noncePool { + if err := m.unsafeRefill(ctx, address); err != nil { + return err + } + } + return nil +} + +func (m *NonceManagerV2) StartAutoRefill(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case <-time.After(poolAutoRefillInterval): + err := m.refillAll(ctx) + if err != nil { + log.Error().Err(err).Msg("Failed to refill nonce pool") + } + } + } +} diff --git a/node/pkg/chain/tests/chain_test.go b/node/pkg/chain/tests/chain_test.go index 6bbbbb2a1..e52aceb45 100644 --- a/node/pkg/chain/tests/chain_test.go +++ b/node/pkg/chain/tests/chain_test.go @@ -30,17 +30,7 @@ var ( func TestNewKaiaHelper(t *testing.T) { ctx := context.Background() - err := db.QueryWithoutResult(ctx, InsertProviderUrlQuery, map[string]any{ - "chain_id": 1001, - "url": "https://public-en-kairos.node.kaia.io", - "priority": 1, - }) - if err != nil { - t.Errorf("Unexpected error: %v", err) - - } - err = db.QueryWithoutResult(ctx, InsertProviderUrlQuery, map[string]any{ "chain_id": 1001, "url": "https://public-en-kairos.node.kaia.io", "priority": 2, @@ -49,12 +39,10 @@ func TestNewKaiaHelper(t *testing.T) { t.Errorf("Unexpected error: %v", err) } - noncemanager.ResetInstance() kaiaHelper, err := helper.NewChainHelper(ctx) if err != nil { t.Errorf("Unexpected error: %v", err) } - assert.Equal(t, 3, kaiaHelper.NumClients()) kaiaHelper.Close() err = db.QueryWithoutResult(ctx, "DELETE FROM provider_urls;", nil) @@ -63,9 +51,9 @@ func TestNewKaiaHelper(t *testing.T) { } } -func TestNewChainHelper(t *testing.T) { +func TestNewSigner(t *testing.T) { ctx := context.Background() - noncemanager.ResetInstance() + _, err := helper.NewSigner(ctx) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -76,14 +64,6 @@ func TestNewEthHelper(t *testing.T) { ctx := context.Background() err := db.QueryWithoutResult(ctx, InsertProviderUrlQuery, map[string]any{ - "chain_id": 11155111, - "url": "https://sepolia.gateway.tenderly.co", - "priority": 1, - }) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - err = db.QueryWithoutResult(ctx, InsertProviderUrlQuery, map[string]any{ "chain_id": 11155111, "url": "wss://ethereum-sepolia-rpc.publicnode.com", "priority": 2, @@ -92,12 +72,10 @@ func TestNewEthHelper(t *testing.T) { t.Errorf("Unexpected error: %v", err) } - noncemanager.ResetInstance() ethHelper, err := helper.NewChainHelper(ctx, helper.WithBlockchainType(helper.Ethereum)) if err != nil { t.Errorf("Unexpected error: %v", err) } - assert.Equal(t, 3, ethHelper.NumClients()) ethHelper.Close() err = db.QueryWithoutResult(ctx, "DELETE FROM provider_urls;", nil) if err != nil { @@ -107,13 +85,15 @@ func TestNewEthHelper(t *testing.T) { func TestMakeDirectTx(t *testing.T) { ctx := context.Background() - noncemanager.ResetInstance() + kaiaHelper, err := helper.NewChainHelper(ctx) if err != nil { t.Errorf("Unexpected error: %v", err) } defer kaiaHelper.Close() + t.Log("helper initialized") + tests := []struct { name string contractAddress string @@ -141,6 +121,7 @@ func TestMakeDirectTx(t *testing.T) { } for _, test := range tests { + t.Log(test.name) directTx, err := kaiaHelper.MakeDirectTx(ctx, test.contractAddress, test.functionString) if err != nil { if err.Error() != test.expectedError.Error() { @@ -156,7 +137,6 @@ func TestMakeDirectTx(t *testing.T) { func TestMakeFeeDelegatedTx(t *testing.T) { ctx := context.Background() - noncemanager.ResetInstance() kaiaHelper, err := helper.NewChainHelper(ctx) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -204,7 +184,7 @@ func TestMakeFeeDelegatedTx(t *testing.T) { func TestTxToHashToTx(t *testing.T) { ctx := context.Background() - noncemanager.ResetInstance() + kaiaHelper, err := helper.NewChainHelper(ctx) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -259,16 +239,17 @@ func TestGenerateViewABI(t *testing.T) { assert.NotEqual(t, abi, nil) } -func TestSubmitDelegetedFallbackDirect(t *testing.T) { +func TestSubmitDelegeted(t *testing.T) { + t.Skip() ctx := context.Background() - noncemanager.ResetInstance() + kaiaHelper, err := helper.NewChainHelper(ctx) if err != nil { t.Errorf("Unexpected error: %v", err) } defer kaiaHelper.Close() - err = kaiaHelper.SubmitDelegatedFallbackDirect(ctx, "0x93120927379723583c7a0dd2236fcb255e96949f", "increment()", maxTxSubmissionRetries) + err = kaiaHelper.SubmitDelegatedFallbackDirect(ctx, "0x93120927379723583c7a0dd2236fcb255e96949f", "increment()") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -292,7 +273,7 @@ func TestSubmitDelegetedFallbackDirectConcurrent(t *testing.T) { submitTx := func() { defer wg.Done() - err := kaiaHelper.SubmitDelegatedFallbackDirect(ctx, "0x93120927379723583c7a0dd2236fcb255e96949f", "increment()", maxTxSubmissionRetries) + err := kaiaHelper.SubmitDelegatedFallbackDirect(ctx, "0x93120927379723583c7a0dd2236fcb255e96949f", "increment()") errCh <- err } @@ -313,7 +294,6 @@ func TestSubmitDelegetedFallbackDirectConcurrent(t *testing.T) { func TestReadContract(t *testing.T) { // testing based on baobab testnet ctx := context.Background() - noncemanager.ResetInstance() kaiaHelper, err := helper.NewChainHelper(ctx) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -343,7 +323,6 @@ func TestReadContract(t *testing.T) { func TestReadContractWithEthHelper(t *testing.T) { // testing based on sepolia eth testnet ctx := context.Background() - noncemanager.ResetInstance() ethHelper, err := helper.NewChainHelper(ctx, helper.WithBlockchainType(helper.Ethereum)) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -715,7 +694,6 @@ func TestSignerRenew(t *testing.T) { t.Skip("Skipping test because SUBMISSION_PROXY_CONTRACT is not set") } - noncemanager.ResetInstance() s, err := helper.NewSigner(ctx) if err != nil { t.Errorf("Unexpected error: %v", err) diff --git a/node/pkg/checker/inspect/app.go b/node/pkg/checker/inspect/app.go index 8f80eeb3d..28e1e18e8 100644 --- a/node/pkg/checker/inspect/app.go +++ b/node/pkg/checker/inspect/app.go @@ -96,7 +96,7 @@ func Setup(ctx context.Context) (*Inspector, error) { return nil, errors.New("missing INSPECT_CONSUMER_ADDRESS") } - chainHelper, err := helper.NewChainHelper(ctx, helper.WithReporterPk(pk), helper.WithoutAdditionalProviderUrls()) + chainHelper, err := helper.NewChainHelper(ctx, helper.WithReporterPk(pk)) if err != nil { return nil, err } diff --git a/node/pkg/por/app.go b/node/pkg/por/app.go index 84900a5f7..52e70bf21 100644 --- a/node/pkg/por/app.go +++ b/node/pkg/por/app.go @@ -75,7 +75,6 @@ func New(ctx context.Context) (*App, error) { helper.WithBlockchainType(helper.Kaia), helper.WithReporterPk(porReporterPk), helper.WithProviderUrl(providerUrl), - helper.WithoutAdditionalProviderUrls(), ) if err != nil { return nil, err diff --git a/node/pkg/reporter/app.go b/node/pkg/reporter/app.go index 98a929a6d..5cb196fb0 100644 --- a/node/pkg/reporter/app.go +++ b/node/pkg/reporter/app.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "sync" + "time" "bisonai.com/miko/node/pkg/chain/helper" errorSentinel "bisonai.com/miko/node/pkg/error" @@ -50,7 +51,7 @@ func (a *App) setReporters(ctx context.Context) error { return errorSentinel.ErrReporterSubmissionProxyContractNotFound } - chainHelper, err := helper.NewChainHelper(ctx, helper.WithoutAdditionalProviderUrls()) + chainHelper, err := helper.NewChainHelper(ctx) if err != nil { log.Error().Str("Player", "Reporter").Err(err).Msg("failed to create chain helper") return err @@ -123,6 +124,7 @@ func (a *App) startReporters(ctx context.Context) { for _, reporter := range a.Reporters { go reporter.Run(ctx) + time.Sleep(2 * time.Second) // sleep for less concurrency } } diff --git a/node/pkg/reporter/reporter.go b/node/pkg/reporter/reporter.go index 2d9fb30ee..0aa60e88c 100644 --- a/node/pkg/reporter/reporter.go +++ b/node/pkg/reporter/reporter.go @@ -136,7 +136,7 @@ func (r *Reporter) report(ctx context.Context, pairs map[string]SubmissionData) wg.Add(1) go func() { defer wg.Done() - err := r.KaiaHelper.SubmitDelegatedFallbackDirect(ctx, r.contractAddress, SUBMIT_WITH_PROOFS, maxTxSubmissionRetries, batchFeedHashes, batchValues, batchTimestamps, batchProofs) + err := r.KaiaHelper.SubmitDelegatedFallbackDirect(ctx, r.contractAddress, SUBMIT_WITH_PROOFS, batchFeedHashes, batchValues, batchTimestamps, batchProofs) if err != nil { errorsChan <- err } diff --git a/node/script/test_submission/main.go b/node/script/test_submission/main.go index c57d551f4..1d2986a9f 100644 --- a/node/script/test_submission/main.go +++ b/node/script/test_submission/main.go @@ -19,7 +19,7 @@ func testContractFeeDelegatedCall(ctx context.Context, contractAddress string, c return err } - return kaiaHelper.SubmitDelegatedFallbackDirect(ctx, contractAddress, contractFunction, maxTxSubmissionRetries, args...) + return kaiaHelper.SubmitDelegatedFallbackDirect(ctx, contractAddress, contractFunction, args...) } func main() {