Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ and https://github.com/cosmos/cosmos-s

### State Machine Breaking

* (x/oracle) Persist the live cycle-list query data so `GetCurrentQueryInCycleList` no longer indexes `GetCyclelist()[Peek()]`. Oracle consensus version 6 backfills prefix 47. Fixes halt after `MsgUpdateCyclelist` shrinks the list (v6.1.8).
* (x/oracle) Remove `MsgBatchSubmitValue`, `SubmitValueItem`, and `MsgUpdateMaxBatchSize`. Oracle consensus version 5 deletes leftover `MaxBatchSize` store bytes. Reporters submit values with `MsgSubmitValue` only (v6.1.7).

### Client Breaking
Expand Down
13 changes: 0 additions & 13 deletions app/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"

"github.com/tellor-io/layer/app/upgrades"
v_6_1_7 "github.com/tellor-io/layer/app/upgrades/v6.1.7"
v_6_1_8 "github.com/tellor-io/layer/app/upgrades/v6.1.8"

upgradetypes "cosmossdk.io/x/upgrade/types"
Expand All @@ -14,7 +13,6 @@ var (
// `Upgrades` defines the upgrade handlers and store loaders for the application.
// New upgrades should be added to this slice after they are implemented.
Upgrades = []*upgrades.Upgrade{
&v_6_1_7.Upgrade,
&v_6_1_8.Upgrade,
}
Forks = []upgrades.Fork{}
Expand All @@ -23,17 +21,6 @@ var (
// setupUpgradeHandlers registers the upgrade handlers to perform custom upgrade
// logic and state migrations for software upgrades.
func (app *App) setupUpgradeHandlers() {
if app.UpgradeKeeper.HasHandler(v_6_1_7.UpgradeName) {
panic(fmt.Sprintf("Cannot register duplicate upgrade handler '%s'", v_6_1_7.UpgradeName))
}
app.UpgradeKeeper.SetUpgradeHandler(
v_6_1_7.UpgradeName,
v_6_1_7.CreateUpgradeHandler(
app.ModuleManager(),
app.configurator,
),
)

if app.UpgradeKeeper.HasHandler(v_6_1_8.UpgradeName) {
panic(fmt.Sprintf("Cannot register duplicate upgrade handler '%s'", v_6_1_8.UpgradeName))
}
Expand Down
5 changes: 4 additions & 1 deletion app/upgrades/v6.1.8/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ Upgrade to v6.1.8 includes (since v6.1.7):
- The last-consensus map is backfilled by bridge 5→6 from the latest
consensus aggregate per queryId (current PowerThreshold, flagged included);
live writes are in CreateNewReportSnapshots; prefix 24; StoreUpgrades empty.

- Oracle cycle-list: persist the live query data in CurrentCycleListQuery (prefix 47)
so GetCurrentQueryInCycleList no longer indexes GetCyclelist()[Peek()]. Oracle
consensus version 5→6 backfills the item from the sequencer with a wrap on OOB.
StoreUpgrades stays empty (new prefix in the existing oracle store).
No other custom state migration is required beyond RunMigrations.
*/

Expand Down
78 changes: 50 additions & 28 deletions e2e/attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,51 +153,73 @@ func TestConsensusAttestation(t *testing.T) {
require.Equal(attestationDataRes.LastConsensusTimestamp, timestamp) // lastConsTs should equal report1 timestamp
}

// sleep until cycle list query data is the same as the previous report
// Wait until the same cycle-list query is live again, then report from only
// one validator (non-consensus). SpotPrice windows are 2 blocks and the
// cycle list has only 3 queries, so SubmitCycleList's re-query can miss the
// window and land on a different query — leaving get-current-aggregate on
// the first 100% report. Submit the matched QueryData directly and retry
// if the window already expired.
prevTimestamp := timestamp
var success bool
deadline := time.Now().Add(2 * time.Minute)
for !success {
require.True(time.Now().Before(deadline), "timed out waiting for non-consensus second report")

cycleListRes, _, err := e2e.QueryWithTimeout(ctx, validators[0].Node, "oracle", "current-cyclelist-query")
require.NoError(err)
var cycleList e2e.QueryCurrentCyclelistQueryResponse
err = json.Unmarshal(cycleListRes, &cycleList)
require.NoError(err)
if cycleList.QueryData == currentCycleList.QueryData {
success = true
// Report for the cycle list from 1 val so not a consensus report
txHash, err := e2e.SubmitCycleList(ctx, validators[0].Node, validators[0].AccAddr, value, "5loya")
require.NoError(err)
fmt.Println("validator [ 0 ] reported at tx:", txHash)
if cycleList.QueryData != currentCycleList.QueryData {
time.Sleep(500 * time.Millisecond)
continue
}

// Wait 1 block for report to be included, then 1 for aggregation
err = testutil.WaitForBlocks(ctx, 2, validators[0].Node)
require.NoError(err)
} else {
txHashBytes, _, err := validators[0].Node.Exec(ctx,
validators[0].Node.TxCommand(validators[0].AccAddr, "oracle", "submit-value", currentCycleList.QueryData, value, "--fees", "5loya", "--keyring-dir", validators[0].Node.HomeDir()),
validators[0].Node.Chain.Config().Env)
if err != nil {
fmt.Println("second-round submit failed (likely window expired), retrying:", err)
time.Sleep(500 * time.Millisecond)
continue
}
}
txHash, err := e2e.GetTxHashFromExec(txHashBytes)
if err != nil {
fmt.Println("second-round submit tx failed, retrying:", err)
time.Sleep(500 * time.Millisecond)
continue
}
fmt.Println("validator [ 0 ] reported at tx:", txHash)

// wait 1 more block
err = testutil.WaitForBlocks(ctx, 1, validators[0].Node)
require.NoError(err)
// Wait for inclusion + aggregation, then confirm current aggregate moved
// to a new timestamp with 50% power before continuing.
require.NoError(testutil.WaitForBlocks(ctx, 3, validators[0].Node))
for i := 0; i < 10; i++ {
res, _, err = e2e.QueryWithTimeout(ctx, validators[0].Node, "oracle", "get-current-aggregate-report", queryId1)
require.NoError(err)
err = json.Unmarshal(res, &currentAggRes)
require.NoError(err)
if currentAggRes.Timestamp != prevTimestamp && currentAggRes.Aggregate != nil && currentAggRes.Aggregate.AggregatePower == "5000000" {
success = true
break
}
require.NoError(testutil.WaitForBlocks(ctx, 1, validators[0].Node))
}
if !success {
fmt.Println("current aggregate not yet non-consensus for queryId1; waiting for next cycle")
}
}

// get reports by reporter
var queryId3 string
reports, _, err := e2e.QueryWithTimeout(ctx, validators[0].Node, "oracle", "get-reportsby-reporter", validators[0].AccAddr, "--page-limit", "2")
// Newest report must be for the same queryId as the first consensus report.
reports, _, err := e2e.QueryWithTimeout(ctx, validators[0].Node, "oracle", "get-reportsby-reporter", validators[0].AccAddr, "--page-limit", "1", "--page-reverse")
require.NoError(err)
var reportsRes e2e.QueryMicroReportsResponse
err = json.Unmarshal(reports, &reportsRes)
require.NoError(err)
fmt.Println("reports from: ", validators[0].AccAddr, ": ", reportsRes)
require.Equal(len(reportsRes.MicroReports), 2) // val0 should have two reports now
queryId3 = reportsRes.MicroReports[0].QueryID
require.Equal(queryId1, queryId3) // make sure query is same as first report, then we can reuse the decoded queryId from earlier
fmt.Println("newest report from: ", validators[0].AccAddr, ": ", reportsRes)
require.Equal(len(reportsRes.MicroReports), 1)
require.Equal(reportsRes.MicroReports[0].QueryID, queryId1)

// query GetCurrentAggregateReport to get aggregate timestamp
res, _, err = e2e.QueryWithTimeout(ctx, validators[0].Node, "oracle", "get-current-aggregate-report", queryId1)
require.NoError(err)
err = json.Unmarshal(res, &currentAggRes)
require.NoError(err)
prevTimestamp := timestamp
timestamp = currentAggRes.Timestamp
fmt.Println("timestamp: ", timestamp)
fmt.Println("currentAggRes: ", currentAggRes)
Expand Down
14 changes: 7 additions & 7 deletions tests/integration/tbr_distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -664,9 +664,14 @@ func (s *IntegrationTestSuite) TestPerAggregatePowerShareNaturalFlow() {
// Advance block height so any old queries are clearly expired
ctx = ctx.WithBlockHeight(5)

// Reset cycle to start fresh: set sequencer to 2, run EndBlocker which wraps to 0
cyclelist, err := s.Setup.Oraclekeeper.GetCyclelist(ctx)
s.NoError(err)
require.Equal(len(cyclelist), 3, "Need at least 3 queries in cyclelist")

// Reset cycle to start fresh: put the live item on the last query with a
// matching sequencer, then EndBlocker wraps to q[0] and initializes QueryMeta.
s.NoError(s.Setup.Oraclekeeper.CurrentCycleListQuery.Set(ctx, cyclelist[2]))
s.NoError(s.Setup.Oraclekeeper.CyclelistSequencer.Set(ctx, 2))
// Run EndBlocker to properly rotate and set up Q1 with fresh expiration
_, err = s.Setup.App.EndBlocker(ctx)
s.NoError(err)

Expand Down Expand Up @@ -695,11 +700,6 @@ func (s *IntegrationTestSuite) TestPerAggregatePowerShareNaturalFlow() {

oracleMsgServer := keeper.NewMsgServerImpl(s.Setup.Oraclekeeper)

// Get cyclelist for comparison
cyclelist, err := s.Setup.Oraclekeeper.GetCyclelist(ctx)
s.NoError(err)
require.Equal(len(cyclelist), 3, "Need at least 3 queries in cyclelist")

// Track queries
queryCount := 0
queriesSubmitted := make(map[string]bool)
Expand Down
29 changes: 17 additions & 12 deletions x/oracle/keeper/cycle_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/hex"
"errors"
"fmt"
"strconv"

"github.com/tellor-io/layer/utils"
Expand Down Expand Up @@ -53,6 +54,9 @@ func (k Keeper) RotateQueries(ctx context.Context) error {
return err
}
max := len(q)
if max == 0 {
return fmt.Errorf("cycle list is empty")
}

switch {
case n >= uint64(max-1): // n could be gt if the cycle list is updated, otherwise n == max-1
Expand All @@ -71,6 +75,9 @@ func (k Keeper) RotateQueries(ctx context.Context) error {
}
// next query
queryId = utils.QueryIDFromData(q[n])
if err := k.CurrentCycleListQuery.Set(ctx, q[n]); err != nil {
return err
}

// increment query opportunities for liveness tracking
if err := k.IncrementQueryOpportunities(ctx, queryId); err != nil {
Expand Down Expand Up @@ -159,17 +166,7 @@ func emitRotateQueriesEvent(sdkCtx sdk.Context, queryId, nextId string) {
}

func (k Keeper) GetCurrentQueryInCycleList(ctx context.Context) ([]byte, error) {
idx, err := k.CyclelistSequencer.Peek(ctx)
if err != nil {
return nil, err
}

q, err := k.GetCyclelist(ctx)
if err != nil {
return nil, err
}

return q[idx], nil
return k.CurrentCycleListQuery.Get(ctx)
}

func (k Keeper) GetNextCurrentQueryInCycleList(ctx context.Context) ([]byte, error) {
Expand Down Expand Up @@ -209,5 +206,13 @@ func (k Keeper) GenesisCycleList(ctx context.Context, cyclelist [][]byte) error
return err
}
}
return nil
q, err := k.GetCyclelist(ctx)
if err != nil {
return err
}
if len(q) == 0 {
return fmt.Errorf("cycle list is empty")
}
// Map iteration order (keyed by queryId), not the input slice order.
return k.CurrentCycleListQuery.Set(ctx, q[0])
}
Loading
Loading