diff --git a/pkg/solana/config/config.go b/pkg/solana/config/config.go index df5be635f..9d5cdc5a9 100644 --- a/pkg/solana/config/config.go +++ b/pkg/solana/config/config.go @@ -27,7 +27,8 @@ var defaultConfigSet = configSet{ ComputeUnitPriceMax: 1_000, ComputeUnitPriceMin: 0, ComputeUnitPriceDefault: 0, - FeeBumpPeriod: 3 * time.Second, + FeeBumpPeriod: 3 * time.Second, // set to 0 to disable fee bumping + BlockHistoryPollPeriod: 5 * time.Second, } //go:generate mockery --name Config --output ./mocks/ --case=underscore --filename config.go @@ -49,6 +50,7 @@ type Config interface { ComputeUnitPriceMin() uint64 ComputeUnitPriceDefault() uint64 FeeBumpPeriod() time.Duration + BlockHistoryPollPeriod() time.Duration } // opt: remove @@ -69,6 +71,7 @@ type configSet struct { ComputeUnitPriceMin uint64 ComputeUnitPriceDefault uint64 FeeBumpPeriod time.Duration + BlockHistoryPollPeriod time.Duration } type Chain struct { @@ -87,6 +90,7 @@ type Chain struct { ComputeUnitPriceMin *uint64 ComputeUnitPriceDefault *uint64 FeeBumpPeriod *config.Duration + BlockHistoryPollPeriod *config.Duration } func (c *Chain) SetDefaults() { @@ -136,6 +140,9 @@ func (c *Chain) SetDefaults() { if c.FeeBumpPeriod == nil { c.FeeBumpPeriod = config.MustNewDuration(defaultConfigSet.FeeBumpPeriod) } + if c.BlockHistoryPollPeriod == nil { + c.BlockHistoryPollPeriod = config.MustNewDuration(defaultConfigSet.BlockHistoryPollPeriod) + } } type Node struct { diff --git a/pkg/solana/config/mocks/config.go b/pkg/solana/config/mocks/config.go index f9f35c3a5..cf1c985a6 100644 --- a/pkg/solana/config/mocks/config.go +++ b/pkg/solana/config/mocks/config.go @@ -28,6 +28,20 @@ func (_m *Config) BalancePollPeriod() time.Duration { return r0 } +// BlockHistoryPollPeriod provides a mock function with given fields: +func (_m *Config) BlockHistoryPollPeriod() time.Duration { + ret := _m.Called() + + var r0 time.Duration + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + // Commitment provides a mock function with given fields: func (_m *Config) Commitment() rpc.CommitmentType { ret := _m.Called() diff --git a/pkg/solana/config/toml.go b/pkg/solana/config/toml.go index 04cf0a08f..e5eb705e6 100644 --- a/pkg/solana/config/toml.go +++ b/pkg/solana/config/toml.go @@ -176,6 +176,9 @@ func setFromChain(c, f *Chain) { if f.FeeBumpPeriod != nil { c.FeeBumpPeriod = f.FeeBumpPeriod } + if f.BlockHistoryPollPeriod != nil { + c.BlockHistoryPollPeriod = f.BlockHistoryPollPeriod + } } func (c *TOMLConfig) ValidateConfig() (err error) { @@ -268,6 +271,10 @@ func (c *TOMLConfig) FeeBumpPeriod() time.Duration { return c.Chain.FeeBumpPeriod.Duration() } +func (c *TOMLConfig) BlockHistoryPollPeriod() time.Duration { + return c.Chain.BlockHistoryPollPeriod.Duration() +} + func (c *TOMLConfig) ListNodes() Nodes { return c.Nodes } diff --git a/pkg/solana/fees/block_history.go b/pkg/solana/fees/block_history.go index d10dd1a04..214612e90 100644 --- a/pkg/solana/fees/block_history.go +++ b/pkg/solana/fees/block_history.go @@ -15,10 +15,6 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" ) -var ( - feePolling = 5 * time.Second // TODO: make configurable -) - var _ Estimator = &blockHistoryEstimator{} type blockHistoryEstimator struct { @@ -70,7 +66,7 @@ func (bhe *blockHistoryEstimator) run() { } } - tick = time.After(utils.WithJitter(feePolling)) + tick = time.After(utils.WithJitter(bhe.cfg.BlockHistoryPollPeriod())) } } diff --git a/pkg/solana/fees/block_history_test.go b/pkg/solana/fees/block_history_test.go index 251ab3edd..a11eff81c 100644 --- a/pkg/solana/fees/block_history_test.go +++ b/pkg/solana/fees/block_history_test.go @@ -22,7 +22,6 @@ import ( ) func TestBlockHistoryEstimator(t *testing.T) { - feePolling = 100 * time.Millisecond // TODO: make this part of cfg mock min := uint64(10) max := uint64(1000) @@ -34,6 +33,7 @@ func TestBlockHistoryEstimator(t *testing.T) { cfg.On("ComputeUnitPriceDefault").Return(uint64(100)) cfg.On("ComputeUnitPriceMin").Return(min) cfg.On("ComputeUnitPriceMax").Return(max) + cfg.On("BlockHistoryPollPeriod").Return(100 * time.Millisecond) lgr, logs := logger.TestObserved(t, zapcore.DebugLevel) ctx := tests.Context(t) diff --git a/pkg/solana/txm/txm.go b/pkg/solana/txm/txm.go index e333f9bbd..6824cef7f 100644 --- a/pkg/solana/txm/txm.go +++ b/pkg/solana/txm/txm.go @@ -237,8 +237,8 @@ func (txm *Txm) sendWithRetry(chanCtx context.Context, baseTx solanaGo.Transacti return case <-tick: var shouldBump bool - // TODO: be able to enable / disable bumping - if time.Since(bumpTime) > txm.cfg.FeeBumpPeriod() { + // bump if period > 0 and past time + if txm.cfg.FeeBumpPeriod() != 0 && time.Since(bumpTime) > txm.cfg.FeeBumpPeriod() { bumpCount++ bumpTime = time.Now() shouldBump = true diff --git a/pkg/solana/txm/txm_internal_test.go b/pkg/solana/txm/txm_internal_test.go index 85b04e4bc..3f370898e 100644 --- a/pkg/solana/txm/txm_internal_test.go +++ b/pkg/solana/txm/txm_internal_test.go @@ -578,6 +578,65 @@ func TestTxm(t *testing.T) { prom.success++ prom.assertEqual(t) }) + + // fee bumping disabled + t.Run("feeBumpingDisabled", func(t *testing.T) { + sig := getSig() + tx, signed := getTx(t, 11, mkey, 0) + + // disable fee bumping + defaultFeeBumpPeriod := cfg.FeeBumpPeriod() + cfg.Chain.FeeBumpPeriod = relayconfig.MustNewDuration(0) + defer func() { + cfg.Chain.FeeBumpPeriod = relayconfig.MustNewDuration(defaultFeeBumpPeriod) // reset + }() + + sendCount := 0 + var countRW sync.RWMutex + mc.On("SendTx", mock.Anything, signed(0)).Run(func(mock.Arguments) { + countRW.Lock() + sendCount++ + countRW.Unlock() + }).Return(sig, nil) // only sends one transaction type (no bumping) + mc.On("SimulateTx", mock.Anything, signed(0), mock.Anything).Return(&rpc.SimulateTransactionResult{}, nil).Once() + + // handle signature status calls + var wg sync.WaitGroup + wg.Add(1) + count := 0 + start := time.Now() + statuses[sig] = func() (out *rpc.SignatureStatusesResult) { + defer func() { count++ }() + + out = &rpc.SignatureStatusesResult{} + if time.Since(start) > 2*defaultFeeBumpPeriod { + out.ConfirmationStatus = rpc.ConfirmationStatusConfirmed + wg.Done() + return + } + out.ConfirmationStatus = rpc.ConfirmationStatusProcessed + return + } + + // send tx + assert.NoError(t, txm.Enqueue(t.Name(), tx)) + wg.Wait() + + // no transactions stored inflight txs list + waitFor(empty) + // transaction should be sent more than twice + countRW.RLock() + t.Logf("sendTx received %d calls", sendCount) + assert.Greater(t, sendCount, 2) + countRW.RUnlock() + + // panic if sendTx called after context cancelled + mc.On("SendTx", mock.Anything, tx).Panic("SendTx should not be called anymore").Maybe() + + // check prom metric + prom.success++ + prom.assertEqual(t) + }) }) } }