diff --git a/cmd/arc/services/metamorph.go b/cmd/arc/services/metamorph.go index f8a5c1677..45f4b323a 100644 --- a/cmd/arc/services/metamorph.go +++ b/cmd/arc/services/metamorph.go @@ -138,7 +138,7 @@ func StartMetamorph(logger *slog.Logger, arcConfig *config.ArcConfig, cacheStore metamorph.WithSubmittedTxsChan(submittedTxsChan), metamorph.WithProcessStatusUpdatesInterval(mtmConfig.ProcessStatusUpdateInterval), metamorph.WithCallbackSender(callbacker), - metamorph.WithStatTimeLimits(mtmConfig.Stats.NotSeenTimeLimit, mtmConfig.Stats.NotMinedTimeLimit), + metamorph.WithStatTimeLimits(mtmConfig.Stats.NotSeenTimeLimit, mtmConfig.Stats.NotFinalTimeLimit), metamorph.WithMaxRetries(mtmConfig.MaxRetries), metamorph.WithMinimumHealthyConnections(mtmConfig.Health.MinimumHealthyConnections)) diff --git a/config/config.go b/config/config.go index 55e13fcfc..d69a20238 100644 --- a/config/config.go +++ b/config/config.go @@ -159,7 +159,7 @@ type HealthConfig struct { type StatsConfig struct { NotSeenTimeLimit time.Duration `mapstructure:"notSeenTimeLimit"` - NotMinedTimeLimit time.Duration `mapstructure:"notMinedTimeLimit"` + NotFinalTimeLimit time.Duration `mapstructure:"notFinalTimeLimit"` } type APIConfig struct { diff --git a/config/defaults.go b/config/defaults.go index 9eeb98c71..dc87fe6bc 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -101,7 +101,7 @@ func getMetamorphConfig() *MetamorphConfig { RejectCallbackContaining: []string{"http://localhost", "https://localhost"}, Stats: &StatsConfig{ NotSeenTimeLimit: 10 * time.Minute, - NotMinedTimeLimit: 20 * time.Minute, + NotFinalTimeLimit: 20 * time.Minute, }, } } diff --git a/config/example_config.yaml b/config/example_config.yaml index 83ba6f17a..d97c9e4d7 100644 --- a/config/example_config.yaml +++ b/config/example_config.yaml @@ -84,7 +84,7 @@ metamorph: rejectCallbackContaining: [ "http://localhost", "https://localhost" ] stats: notSeenTimeLimit: 10m # amount of time after storing at which a non-seen tx will be counted towards not seen stat - notMinedTimeLimit: 20m # amount of time after storing at which a seen but not mined tx will be counted towards not mined stat + notFinalTimeLimit: 20m # amount of time after storing at which a seen but not mined tx will be counted towards not mined stat blocktx: listenAddr: localhost:8011 # address space for blocktx to listen on. Can be for example localhost:8011 or :8011 for listening on all addresses diff --git a/internal/metamorph/processor_options.go b/internal/metamorph/processor_options.go index b1b278bd1..babe91a0e 100644 --- a/internal/metamorph/processor_options.go +++ b/internal/metamorph/processor_options.go @@ -11,9 +11,9 @@ import ( "github.com/bitcoin-sv/arc/internal/metamorph/metamorph_api" ) -func WithStatTimeLimits(notSeenLimit time.Duration, notMinedLimit time.Duration) func(*Processor) { +func WithStatTimeLimits(notSeenLimit time.Duration, notFinalLimit time.Duration) func(*Processor) { return func(p *Processor) { - p.stats = newProcessorStats(WithLimits(notSeenLimit, notMinedLimit)) + p.stats = newProcessorStats(WithLimits(notSeenLimit, notFinalLimit)) } } diff --git a/internal/metamorph/stats_collector.go b/internal/metamorph/stats_collector.go index ab447cd7e..21a725456 100644 --- a/internal/metamorph/stats_collector.go +++ b/internal/metamorph/stats_collector.go @@ -21,7 +21,7 @@ var ErrFailedToRegisterStats = fmt.Errorf("failed to register stats collector") type processorStats struct { notSeenLimit time.Duration - notMinedLimit time.Duration + notFinalLimit time.Duration mu sync.RWMutex statusStored prometheus.Gauge @@ -34,16 +34,17 @@ type processorStats struct { statusDoubleSpendAttempted prometheus.Gauge statusRejected prometheus.Gauge statusMined prometheus.Gauge - statusNotMined prometheus.Gauge + statusNotMined prometheus.Gauge // Todo: remove - is replaced by statusNotFinal + statusNotFinal prometheus.Gauge statusNotSeen prometheus.Gauge statusMinedTotal prometheus.Gauge statusSeenOnNetworkTotal prometheus.Gauge } -func WithLimits(notSeenLimit time.Duration, notMinedLimit time.Duration) func(*processorStats) { +func WithLimits(notSeenLimit time.Duration, notFinalLimit time.Duration) func(*processorStats) { return func(p *processorStats) { p.notSeenLimit = notSeenLimit - p.notMinedLimit = notMinedLimit + p.notFinalLimit = notFinalLimit } } @@ -98,7 +99,7 @@ func newProcessorStats(opts ...func(stats *processorStats)) *processorStats { Help: "Total number of monitored transactions with status MINED", }), notSeenLimit: notSeenLimitDefault, - notMinedLimit: notMinedLimitDefault, + notFinalLimit: notMinedLimitDefault, } for _, opt := range opts { @@ -107,7 +108,11 @@ func newProcessorStats(opts ...func(stats *processorStats)) *processorStats { p.statusNotMined = prometheus.NewGauge(prometheus.GaugeOpts{ Name: "arc_status_not_mined_count", - Help: fmt.Sprintf("Number of monitored transactions which are SEEN_ON_NETWORK but haven reached status MINED for more than %s", p.notMinedLimit.String()), + Help: fmt.Sprintf("Number of monitored transactions which are SEEN_ON_NETWORK but haven reached status MINED for more than %s", p.notFinalLimit.String()), + }) + p.statusNotFinal = prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "arc_status_not_final_count", + Help: fmt.Sprintf("Number of monitored transactions which are not in a final state of either MINED or rejected %s", p.notFinalLimit.String()), }) p.statusNotSeen = prometheus.NewGauge(prometheus.GaugeOpts{ Name: "arc_status_not_seen_count", @@ -131,7 +136,7 @@ func (p *Processor) StartCollectStats() error { p.stats.statusDoubleSpendAttempted, p.stats.statusRejected, p.stats.statusMined, - p.stats.statusNotMined, + p.stats.statusNotFinal, p.stats.statusNotSeen, p.stats.statusSeenOnNetworkTotal, p.stats.statusMinedTotal, @@ -160,7 +165,7 @@ func (p *Processor) StartCollectStats() error { p.stats.statusDoubleSpendAttempted, p.stats.statusRejected, p.stats.statusMined, - p.stats.statusNotMined, + p.stats.statusNotFinal, p.stats.statusNotSeen, p.stats.statusSeenOnNetworkTotal, p.stats.statusMinedTotal, @@ -176,7 +181,7 @@ func (p *Processor) StartCollectStats() error { getStatsSince := p.now().Add(-1 * p.mapExpiryTime) - collectedStats, err := p.store.GetStats(p.ctx, getStatsSince, p.stats.notSeenLimit, p.stats.notMinedLimit) + collectedStats, err := p.store.GetStats(p.ctx, getStatsSince, p.stats.notSeenLimit, p.stats.notFinalLimit) if err != nil { p.logger.Error("failed to get stats", slog.String("err", err.Error())) continue @@ -193,7 +198,8 @@ func (p *Processor) StartCollectStats() error { p.stats.statusDoubleSpendAttempted.Set(float64(collectedStats.StatusDoubleSpendAttempted)) p.stats.statusRejected.Set(float64(collectedStats.StatusRejected)) p.stats.statusMined.Set(float64(collectedStats.StatusMined)) - p.stats.statusNotMined.Set(float64(collectedStats.StatusNotMined)) + p.stats.statusNotMined.Set(float64(collectedStats.StatusNotFinal)) + p.stats.statusNotFinal.Set(float64(collectedStats.StatusNotFinal)) p.stats.statusNotSeen.Set(float64(collectedStats.StatusNotSeen)) p.stats.statusSeenOnNetworkTotal.Set(float64(collectedStats.StatusSeenOnNetworkTotal)) p.stats.statusMinedTotal.Set(float64(collectedStats.StatusMinedTotal)) diff --git a/internal/metamorph/store/postgresql/postgres.go b/internal/metamorph/store/postgresql/postgres.go index 2165029bb..385c58e1b 100644 --- a/internal/metamorph/store/postgresql/postgres.go +++ b/internal/metamorph/store/postgresql/postgres.go @@ -934,7 +934,7 @@ func (p *PostgreSQL) ClearData(ctx context.Context, retentionDays int32) (int64, return rows, nil } -func (p *PostgreSQL) GetStats(ctx context.Context, since time.Time, notSeenLimit time.Duration, notMinedLimit time.Duration) (*store.Stats, error) { +func (p *PostgreSQL) GetStats(ctx context.Context, since time.Time, notSeenLimit time.Duration, notFinalLimit time.Duration) (*store.Stats, error) { q := ` SELECT max(status_counts.status_count) FILTER (where status_counts.status = $3 ) @@ -1043,20 +1043,20 @@ func (p *PostgreSQL) GetStats(ctx context.Context, since time.Time, notSeenLimit WHERE t.last_submitted_at > $1 AND status < $2 AND t.locked_by = $3 AND $4 - t.stored_at > $5 ` - err = p.db.QueryRowContext(ctx, qNotSeen, since, metamorph_api.Status_SEEN_ON_NETWORK, p.hostname, p.now(), notSeenLimit.Seconds()).Scan(&stats.StatusNotSeen) + err = p.db.QueryRowContext(ctx, qNotSeen, since, metamorph_api.Status_SEEN_IN_ORPHAN_MEMPOOL, p.hostname, p.now(), notSeenLimit.Seconds()).Scan(&stats.StatusNotSeen) if err != nil { return nil, err } - qNotMined := ` + qNotFinal := ` SELECT count(*) FROM metamorph.transactions t - WHERE t.last_submitted_at > $1 AND status = $2 AND t.locked_by = $3 - AND EXTRACT(EPOCH FROM ($4 - t.stored_at)) > $5 + WHERE t.last_submitted_at > $1 AND status >= $2 AND status <$3 AND t.locked_by = $4 + AND EXTRACT(EPOCH FROM ($5 - t.stored_at)) > $6 ` - err = p.db.QueryRowContext(ctx, qNotMined, since, metamorph_api.Status_SEEN_ON_NETWORK, p.hostname, p.now(), notMinedLimit.Seconds()).Scan(&stats.StatusNotMined) + err = p.db.QueryRowContext(ctx, qNotFinal, since, metamorph_api.Status_SEEN_ON_NETWORK, metamorph_api.Status_REJECTED, p.hostname, p.now(), notFinalLimit.Seconds()).Scan(&stats.StatusNotFinal) if err != nil { return nil, err } diff --git a/internal/metamorph/store/postgresql/postgres_test.go b/internal/metamorph/store/postgresql/postgres_test.go index 7f75781b4..d03cdabf6 100644 --- a/internal/metamorph/store/postgresql/postgres_test.go +++ b/internal/metamorph/store/postgresql/postgres_test.go @@ -10,16 +10,17 @@ import ( "testing" "time" - "github.com/bitcoin-sv/arc/internal/blocktx/blocktx_api" - "github.com/bitcoin-sv/arc/internal/metamorph/metamorph_api" - "github.com/bitcoin-sv/arc/internal/metamorph/store" - testutils "github.com/bitcoin-sv/arc/internal/test_utils" - "github.com/bitcoin-sv/arc/internal/testdata" _ "github.com/golang-migrate/migrate/v4/source/file" _ "github.com/lib/pq" "github.com/ory/dockertest/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/bitcoin-sv/arc/internal/blocktx/blocktx_api" + "github.com/bitcoin-sv/arc/internal/metamorph/metamorph_api" + "github.com/bitcoin-sv/arc/internal/metamorph/store" + testutils "github.com/bitcoin-sv/arc/internal/test_utils" + "github.com/bitcoin-sv/arc/internal/testdata" ) const ( @@ -714,7 +715,7 @@ func TestPostgresDB(t *testing.T) { require.Equal(t, int64(1), res.StatusRejected) require.Equal(t, int64(0), res.StatusSeenInOrphanMempool) require.Equal(t, int64(1), res.StatusDoubleSpendAttempted) - require.Equal(t, int64(1), res.StatusNotMined) + require.Equal(t, int64(2), res.StatusNotFinal) require.Equal(t, int64(2), res.StatusNotSeen) require.Equal(t, int64(6), res.StatusMinedTotal) require.Equal(t, int64(2), res.StatusSeenOnNetworkTotal) diff --git a/internal/metamorph/store/store.go b/internal/metamorph/store/store.go index 4121e715f..2ecee929e 100644 --- a/internal/metamorph/store/store.go +++ b/internal/metamorph/store/store.go @@ -6,9 +6,10 @@ import ( "errors" "time" + "github.com/libsv/go-p2p/chaincfg/chainhash" + "github.com/bitcoin-sv/arc/internal/blocktx/blocktx_api" "github.com/bitcoin-sv/arc/internal/metamorph/metamorph_api" - "github.com/libsv/go-p2p/chaincfg/chainhash" ) var ErrNotFound = errors.New("key could not be found") @@ -56,7 +57,7 @@ type Stats struct { StatusRejected int64 StatusMined int64 StatusNotSeen int64 - StatusNotMined int64 + StatusNotFinal int64 StatusSeenOnNetworkTotal int64 StatusMinedTotal int64 } diff --git a/test/config/config.yaml b/test/config/config.yaml index b64ca9a6f..cab172f84 100644 --- a/test/config/config.yaml +++ b/test/config/config.yaml @@ -74,7 +74,7 @@ metamorph: rejectCallbackContaining: [ "http://localhost", "https://localhost" ] stats: notSeenTimeLimit: 10m - notMinedTimeLimit: 20m + notFinalTimeLimit: 20m blocktx: listenAddr: 0.0.0.0:8011