From d5e85f2cfd32837498a776fc05863a3438b08db9 Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Fri, 10 Jan 2025 06:55:37 -0500 Subject: [PATCH 1/9] feat(x/tally): fixed payout to committers when data request expires --- x/tally/keeper/endblock.go | 6 ++++++ x/tally/keeper/integration_helpers_test.go | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/x/tally/keeper/endblock.go b/x/tally/keeper/endblock.go index 256725f7..047e349a 100644 --- a/x/tally/keeper/endblock.go +++ b/x/tally/keeper/endblock.go @@ -122,6 +122,12 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err k.Logger(ctx).Info("completed tally", "request_id", req.ID) k.Logger(ctx).Debug("tally result", "request_id", req.ID, "tally_result", tallyResults[i]) + + // TODO + distMsgs = types.DistributionMessages{ + Messages: []types.DistributionMessage{}, + RefundType: types.DistributionTypeNoConsensus, + } } processedReqs[req.ID] = distMsgs diff --git a/x/tally/keeper/integration_helpers_test.go b/x/tally/keeper/integration_helpers_test.go index d4c7a833..b416a746 100644 --- a/x/tally/keeper/integration_helpers_test.go +++ b/x/tally/keeper/integration_helpers_test.go @@ -91,7 +91,11 @@ func (f *fixture) commitRevealDataRequest(t *testing.T, requestMemo, reveal stri f.Context(), f.coreContractAddr, stakers[i].address, +<<<<<<< HEAD revealMsg(drID, reveal, stakers[i].pubKey, proof), +======= + revealMsg(drID, stakers[i].pubKey, proof), +>>>>>>> 1e9f138 (feat(x/tally): fixed payout to committers when data request expires) sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewIntFromUint64(1))), ) require.NoError(t, err) From f480d4d646bdd364692bab0007879668b5317a51 Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Mon, 13 Jan 2025 07:20:25 -0500 Subject: [PATCH 2/9] refactor(x/tally): tally requests with missing reveals and separate filter from keeper --- x/tally/keeper/endblock.go | 6 ------ x/tally/keeper/integration_helpers_test.go | 4 ---- 2 files changed, 10 deletions(-) diff --git a/x/tally/keeper/endblock.go b/x/tally/keeper/endblock.go index 047e349a..256725f7 100644 --- a/x/tally/keeper/endblock.go +++ b/x/tally/keeper/endblock.go @@ -122,12 +122,6 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err k.Logger(ctx).Info("completed tally", "request_id", req.ID) k.Logger(ctx).Debug("tally result", "request_id", req.ID, "tally_result", tallyResults[i]) - - // TODO - distMsgs = types.DistributionMessages{ - Messages: []types.DistributionMessage{}, - RefundType: types.DistributionTypeNoConsensus, - } } processedReqs[req.ID] = distMsgs diff --git a/x/tally/keeper/integration_helpers_test.go b/x/tally/keeper/integration_helpers_test.go index b416a746..d4c7a833 100644 --- a/x/tally/keeper/integration_helpers_test.go +++ b/x/tally/keeper/integration_helpers_test.go @@ -91,11 +91,7 @@ func (f *fixture) commitRevealDataRequest(t *testing.T, requestMemo, reveal stri f.Context(), f.coreContractAddr, stakers[i].address, -<<<<<<< HEAD revealMsg(drID, reveal, stakers[i].pubKey, proof), -======= - revealMsg(drID, stakers[i].pubKey, proof), ->>>>>>> 1e9f138 (feat(x/tally): fixed payout to committers when data request expires) sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewIntFromUint64(1))), ) require.NoError(t, err) From c09883c960dfd20a2884fb9e2bc73bc0caa6336c Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Mon, 13 Jan 2025 13:03:48 -0500 Subject: [PATCH 3/9] feat(x/tally): executor payout in uniform case --- x/tally/keeper/endblock.go | 20 ++++++--- x/tally/keeper/endblock_test.go | 9 +++- x/tally/keeper/integration_helpers_test.go | 7 +-- x/tally/keeper/payout.go | 50 +++++++++++++++++++--- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/x/tally/keeper/endblock.go b/x/tally/keeper/endblock.go index 256725f7..b069533c 100644 --- a/x/tally/keeper/endblock.go +++ b/x/tally/keeper/endblock.go @@ -228,12 +228,22 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. } // Phase III: Calculate Payouts - // TODO: Calculate gas used & payouts - result.ExecGasUsed = calculateExecGasUsed(reveals) - distMsgs := types.DistributionMessages{ - Messages: []types.DistributionMessage{}, - RefundType: types.DistributionTypeNoConsensus, + // TODO guarantee: len(reveals) > 0 + var distMsgs types.DistributionMessages + var gasUsed uint64 + var err error + if req.ReplicationFactor == 1 || areGasReportsUniform(reveals) { + distMsgs.Messages, gasUsed, err = CalculateUniformPayouts(reveals, req.ExecGasLimit, req.ReplicationFactor, req.GasPrice) + } else { + distMsgs.Messages, gasUsed, err = CalculateDivergentPayouts(reveals, req.ExecGasLimit, req.ReplicationFactor, req.GasPrice) + } + if err != nil { + return filterResult, result, types.DistributionMessages{} // TODO } + distMsgs.RefundType = types.DistributionTypeNoConsensus // TODO check + result.ExecGasUsed = gasUsed + // TODO: Requestor refund + return filterResult, result, distMsgs } diff --git a/x/tally/keeper/endblock_test.go b/x/tally/keeper/endblock_test.go index 5a40eb26..dd24420a 100644 --- a/x/tally/keeper/endblock_test.go +++ b/x/tally/keeper/endblock_test.go @@ -93,11 +93,18 @@ func TestEndBlock(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - drID := f.commitRevealDataRequest(t, tt.memo, "Ghkvq84TmIuEmU1ClubNxBjVXi8df5QhiNQEC5T8V6w=", tt.replicationFactor, tt.numCommits, tt.numReveals, tt.timeout) + drID, stakers := f.commitRevealDataRequest(t, tt.memo, "Ghkvq84TmIuEmU1ClubNxBjVXi8df5QhiNQEC5T8V6w=", tt.replicationFactor, tt.numCommits, tt.numReveals, tt.timeout) + + beforeBalance := f.bankKeeper.GetBalance(f.Context(), stakers[0].address, bondDenom) err := f.tallyKeeper.EndBlock(f.Context()) require.NoError(t, err) + // TODO query get_staker pending_withdrawal and check diff + afterBalance := f.bankKeeper.GetBalance(f.Context(), stakers[0].address, bondDenom) + diff := afterBalance.Sub(beforeBalance) + require.Equal(t, "0aseda", diff.String()) + dataResult, err := f.batchingKeeper.GetLatestDataResult(f.Context(), drID) require.NoError(t, err) require.Equal(t, tt.expExitCode, dataResult.ExitCode) diff --git a/x/tally/keeper/integration_helpers_test.go b/x/tally/keeper/integration_helpers_test.go index d4c7a833..9baafea7 100644 --- a/x/tally/keeper/integration_helpers_test.go +++ b/x/tally/keeper/integration_helpers_test.go @@ -30,8 +30,9 @@ const ( // commitRevealDataRequest simulates stakers committing and revealing // for a data request. It returns the data request ID. -func (f *fixture) commitRevealDataRequest(t *testing.T, requestMemo, reveal string, replicationFactor, numCommits, numReveals int, timeout bool) string { +func (f *fixture) commitRevealDataRequest(t *testing.T, requestMemo, reveal string, replicationFactor, numCommits, numReveals int, timeout bool) (string, []staker) { stakers := f.addStakers(t, 5) + f.initAccountWithCoins(t, f.deployer, sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewIntFromUint64(1e18)))) // Upload data request and tally oracle programs. execProgram := wasmstoragetypes.NewOracleProgram(testdata.SampleTallyWasm(), f.Context().BlockTime(), f.Context().BlockHeight(), 1000) @@ -46,7 +47,7 @@ func (f *fixture) commitRevealDataRequest(t *testing.T, requestMemo, reveal stri resJSON, err := f.contractKeeper.Execute( f.Context(), f.coreContractAddr, - stakers[0].address, + f.deployer, postDataRequestMsg(execProgram.Hash, tallyProgram.Hash, requestMemo, replicationFactor), sdk.NewCoins(sdk.NewCoin(bondDenom, math.NewIntFromUint64(3000000000000100))), ) @@ -103,7 +104,7 @@ func (f *fixture) commitRevealDataRequest(t *testing.T, requestMemo, reveal stri } } - return res.DrID + return res.DrID, stakers } type staker struct { diff --git a/x/tally/keeper/payout.go b/x/tally/keeper/payout.go index 4030efec..f44d3c82 100644 --- a/x/tally/keeper/payout.go +++ b/x/tally/keeper/payout.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "sort" "cosmossdk.io/math" @@ -51,11 +52,48 @@ func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, ga return result, nil } -// TODO: This will become more complex when we introduce incentives. -func calculateExecGasUsed(reveals []types.RevealBody) uint64 { - var execGasUsed uint64 - for _, reveal := range reveals { - execGasUsed += reveal.GasUsed +// CalculateUniformPayouts returns payouts for the executors of the given reveals +// and the total gas used for the execution under the uniform reporting scenario. +func CalculateUniformPayouts(reveals []types.RevealBody, execGasLimit uint64, replicationFactor uint16, gasPrice string) ([]types.DistributionMessage, uint64, error) { + gasUsed := max(reveals[0].GasUsed, execGasLimit/uint64(replicationFactor)) + gasPriceInt, ok := math.NewIntFromString(gasPrice) + if !ok { + return nil, 0, fmt.Errorf("invalid gas price: %s", gasPrice) // TODO error } - return execGasUsed + payout := gasPriceInt.Mul(math.NewIntFromUint64(gasUsed)) + + distMsgs := make([]types.DistributionMessage, len(reveals)) + for i, reveal := range reveals { + distMsgs[i] = types.DistributionMessage{ + Kind: types.DistributionKind{ + ExecutorReward: &types.DistributionExecutorReward{ + Identity: reveal.ID, + Amount: payout, + }, + }, + Type: types.DistributionTypeTimedOut, // TODO check + } + } + return distMsgs, gasUsed, nil +} + +// CalculateDivergentPayouts returns payouts for the executors of the given reveals +// and the total gas used for the execution under the divergent reporting scenario. +func CalculateDivergentPayouts(reveals []types.RevealBody, execGasLimit uint64, replicationFactor uint16, gasPrice string) ([]types.DistributionMessage, uint64, error) { + return nil, 0, nil +} + +// areGasReportsUniform returns true if the gas reports of the given reveals are +// uniform. +func areGasReportsUniform(reveals []types.RevealBody) bool { + if len(reveals) == 0 { + return true + } + firstGas := reveals[0].GasUsed + for i := 1; i < len(reveals); i++ { + if reveals[i].GasUsed != firstGas { + return false + } + } + return true } From 023fd1794467217c0592f9d44d39f612656f152c Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Mon, 13 Jan 2025 17:08:04 -0500 Subject: [PATCH 4/9] feat(x/tally): executor payout in case of divergent gas reporting --- x/tally/keeper/endblock.go | 48 +++++--- x/tally/keeper/endblock_test.go | 4 +- x/tally/keeper/filter_and_tally_test.go | 144 ++++++++++++++++++------ x/tally/keeper/payout.go | 71 +++++++++--- x/tally/keeper/tally_vm.go | 1 + 5 files changed, 198 insertions(+), 70 deletions(-) diff --git a/x/tally/keeper/endblock.go b/x/tally/keeper/endblock.go index b069533c..c9c2deb7 100644 --- a/x/tally/keeper/endblock.go +++ b/x/tally/keeper/endblock.go @@ -103,7 +103,8 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err } var distMsgs types.DistributionMessages - if len(req.Commits) < int(req.ReplicationFactor) { + switch { + case len(req.Commits) < int(req.ReplicationFactor): dataResults[i].Result = []byte(fmt.Sprintf("need %d commits; received %d", req.ReplicationFactor, len(req.Commits))) dataResults[i].ExitCode = TallyExitCodeNotEnoughCommits k.Logger(ctx).Info("data request's number of commits did not meet replication factor", "request_id", req.ID) @@ -112,8 +113,22 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err if err != nil { return err } - } else { - _, tallyResults[i], distMsgs = k.FilterAndTally(ctx, req, params) + case len(req.Reveals) == 0: + dataResults[i].Result = []byte(fmt.Sprintf("no reveals")) + dataResults[i].ExitCode = TallyExitCodeNoReveals + k.Logger(ctx).Info("data request has no reveals", "request_id", req.ID) + + distMsgs, err = k.CalculateCommitterPayouts(ctx, req, gasPriceInt) + if err != nil { + return err + } + default: + gasPriceInt, ok := math.NewIntFromString(req.GasPrice) + if !ok { + return fmt.Errorf("invalid gas price: %s", req.GasPrice) // TODO improve error handling + } + + _, tallyResults[i], distMsgs = k.FilterAndTally(ctx, req, params, gasPriceInt) dataResults[i].Result = tallyResults[i].Result //nolint:gosec // G115: We shouldn't get negative exit code anyway. dataResults[i].ExitCode = uint32(tallyResults[i].ExitInfo.ExitCode) @@ -181,10 +196,10 @@ type TallyResult struct { // FilterAndTally builds and applies filter, executes tally program, // and calculates payouts. -func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types.Params) (FilterResult, TallyResult, types.DistributionMessages) { +func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types.Params, gasPrice math.Int) (FilterResult, TallyResult, types.DistributionMessages) { var result TallyResult - // Sort the reveals by their keys. + // Sort the reveals by their keys (executors). keys := make([]string, len(req.Reveals)) i := 0 for k := range req.Reveals { @@ -228,21 +243,18 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. } // Phase III: Calculate Payouts - // TODO guarantee: len(reveals) > 0 var distMsgs types.DistributionMessages - var gasUsed uint64 - var err error - if req.ReplicationFactor == 1 || areGasReportsUniform(reveals) { - distMsgs.Messages, gasUsed, err = CalculateUniformPayouts(reveals, req.ExecGasLimit, req.ReplicationFactor, req.GasPrice) - } else { - distMsgs.Messages, gasUsed, err = CalculateDivergentPayouts(reveals, req.ExecGasLimit, req.ReplicationFactor, req.GasPrice) - } - if err != nil { - return filterResult, result, types.DistributionMessages{} // TODO + if filterErr == nil || errors.Is(filterErr, types.ErrConsensusInError) { + var gasUsed uint64 + if req.ReplicationFactor == 1 || areGasReportsUniform(reveals) { + distMsgs.Messages, gasUsed = CalculateUniformPayouts(keys, reveals[0].GasUsed, req.ExecGasLimit, req.ReplicationFactor, gasPrice) + } else { + distMsgs.Messages, gasUsed = CalculateDivergentPayouts(keys, reveals, req.ExecGasLimit, req.ReplicationFactor, gasPrice) + } + distMsgs.RefundType = types.DistributionTypeExecutorReward // TODO double check + result.ExecGasUsed = gasUsed } - distMsgs.RefundType = types.DistributionTypeNoConsensus // TODO check - result.ExecGasUsed = gasUsed - // TODO: Requestor refund + // TODO: else pay committers? return filterResult, result, distMsgs } diff --git a/x/tally/keeper/endblock_test.go b/x/tally/keeper/endblock_test.go index dd24420a..7a0a50c5 100644 --- a/x/tally/keeper/endblock_test.go +++ b/x/tally/keeper/endblock_test.go @@ -73,13 +73,13 @@ func TestEndBlock(t *testing.T) { expExitCode: keeper.TallyExitCodeNotEnoughCommits, }, { - name: "reveal timeout", + name: "reveal timeout with no reveals", memo: "cmV2ZWFsIHRpbWVvdXQ=", replicationFactor: 2, numCommits: 2, numReveals: 0, timeout: true, - expExitCode: keeper.TallyExitCodeFilterError, + expExitCode: keeper.TallyExitCodeNoReveals, }, { name: "reveal timeout with 2 reveals", diff --git a/x/tally/keeper/filter_and_tally_test.go b/x/tally/keeper/filter_and_tally_test.go index 702ae103..330305ee 100644 --- a/x/tally/keeper/filter_and_tally_test.go +++ b/x/tally/keeper/filter_and_tally_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "testing" + "cosmossdk.io/math" "github.com/stretchr/testify/require" "github.com/sedaprotocol/seda-chain/x/tally/keeper" @@ -26,7 +27,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor uint16 consensus bool consPubKeys []string // expected proxy public keys in basic consensus - gasUsed uint64 + filterGasUsed uint64 exitCode int filterErr error }{ @@ -43,7 +44,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: true, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostNone, + filterGasUsed: defaultParams.FilterGasCostNone, exitCode: keeper.TallyExitCodeExecError, // since tally program does not exist filterErr: nil, }, @@ -57,7 +58,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostNone, + filterGasUsed: defaultParams.FilterGasCostNone, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -74,7 +75,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: true, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierMode * 5, + filterGasUsed: defaultParams.FilterGasCostMultiplierMode * 5, exitCode: keeper.TallyExitCodeExecError, // since tally program does not exist filterErr: nil, }, @@ -88,19 +89,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierMode * 5, - exitCode: keeper.TallyExitCodeFilterError, - filterErr: types.ErrNoBasicConsensus, - }, - { - name: "Mode filter - No reveals", - tallyInputAsHex: "01000000000000000D242E726573756C742E74657874", // json_path = $.result.text - outliers: []bool{}, - reveals: map[string]types.RevealBody{}, - replicationFactor: 5, - consensus: false, - consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierMode * 5, + filterGasUsed: defaultParams.FilterGasCostMultiplierMode * 5, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -117,7 +106,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: true, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierStdDev * 5, + filterGasUsed: defaultParams.FilterGasCostMultiplierStdDev * 5, exitCode: keeper.TallyExitCodeExecError, // since tally program does not exist filterErr: nil, }, @@ -131,19 +120,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierStdDev * 5, - exitCode: keeper.TallyExitCodeFilterError, - filterErr: types.ErrNoBasicConsensus, - }, - { - name: "Standard deviation filter - No reveals", - tallyInputAsHex: "02000000000016E36001000000000000000D242E726573756C742E74657874", // max_sigma = 1.5, number_type = int64, json_path = $.result.text - outliers: []bool{}, - reveals: map[string]types.RevealBody{}, - replicationFactor: 5, - consensus: false, - consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierStdDev * 5, + filterGasUsed: defaultParams.FilterGasCostMultiplierStdDev * 5, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -157,6 +134,7 @@ func TestFilterAndTally(t *testing.T) { for k, v := range tt.reveals { revealBody := v revealBody.Reveal = base64.StdEncoding.EncodeToString([]byte(v.Reveal)) + revealBody.GasUsed = v.GasUsed reveals[k] = revealBody } @@ -164,10 +142,12 @@ func TestFilterAndTally(t *testing.T) { Reveals: reveals, ReplicationFactor: tt.replicationFactor, ConsensusFilter: base64.StdEncoding.EncodeToString(filterInput), - }, types.DefaultParams()) + GasPrice: "1000000000000000000", // 1e18 + ExecGasLimit: 100000, + }, types.DefaultParams(), math.NewInt(1000000000000000000)) require.Equal(t, tt.outliers, filterRes.Outliers) - require.Equal(t, tt.gasUsed, filterRes.GasUsed) + require.Equal(t, tt.filterGasUsed, filterRes.GasUsed) require.Equal(t, tt.consensus, filterRes.Consensus) require.Equal(t, tt.consensus, tallyRes.Consensus) require.Equal(t, tt.exitCode, tallyRes.ExitInfo.ExitCode) @@ -185,3 +165,101 @@ func TestFilterAndTally(t *testing.T) { }) } } + +func TestExecutorPayout(t *testing.T) { + f := initFixture(t) + + defaultParams := types.DefaultParams() + err := f.tallyKeeper.SetParams(f.Context(), defaultParams) + require.NoError(t, err) + + tests := []struct { + name string + tallyInputAsHex string + reveals map[string]types.RevealBody + replicationFactor uint16 + execGasLimit uint64 + expExecGasUsed uint64 + expExecutorRewards map[string]math.Int + }{ + { + name: "3/3 - Uniform gas reporting", + tallyInputAsHex: "00", + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "b": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "c": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + }, + replicationFactor: 3, + execGasLimit: 30000, + expExecGasUsed: 90000, + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(30000, 18), + "b": math.NewIntWithDecimal(30000, 18), + "c": math.NewIntWithDecimal(30000, 18), + }, + }, + { + name: "3/3 - Divergent gas reporting (1)", + tallyInputAsHex: "00", + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 28000}, + "b": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "c": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 32000}, + }, + replicationFactor: 3, + execGasLimit: 90000, + expExecGasUsed: 90000, + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(43448, 18), + "b": math.NewIntWithDecimal(23275, 18), + "c": math.NewIntWithDecimal(23275, 18), + }, + }, + { + name: "3/3 - Divergent gas reporting (2)", + tallyInputAsHex: "00", + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 8000}, + "b": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 20000}, + "c": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 35000}, + }, + replicationFactor: 3, + execGasLimit: 90000, + expExecGasUsed: 56000, + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(16000, 18), + "b": math.NewIntWithDecimal(20000, 18), + "c": math.NewIntWithDecimal(20000, 18), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filterInput, err := hex.DecodeString(tt.tallyInputAsHex) + require.NoError(t, err) + + reveals := make(map[string]types.RevealBody) + for k, v := range tt.reveals { + revealBody := v + revealBody.Reveal = base64.StdEncoding.EncodeToString([]byte(v.Reveal)) + revealBody.GasUsed = v.GasUsed + reveals[k] = revealBody + } + + _, tallyRes, distMsgs := f.tallyKeeper.FilterAndTally(f.Context(), types.Request{ + Reveals: reveals, + ReplicationFactor: tt.replicationFactor, + ConsensusFilter: base64.StdEncoding.EncodeToString(filterInput), + GasPrice: "1000000000000000000", // 1e18 + ExecGasLimit: tt.execGasLimit, + }, types.DefaultParams(), math.NewInt(1000000000000000000)) + + require.Equal(t, tt.expExecGasUsed, tallyRes.ExecGasUsed) + for _, distMsg := range distMsgs.Messages { + require.Equal(t, tt.expExecutorRewards[distMsg.Kind.ExecutorReward.Identity], distMsg.Kind.ExecutorReward.Amount) + require.Equal(t, types.DistributionTypeExecutorReward, distMsg.Type) + } + }) + } +} diff --git a/x/tally/keeper/payout.go b/x/tally/keeper/payout.go index f44d3c82..1501fd16 100644 --- a/x/tally/keeper/payout.go +++ b/x/tally/keeper/payout.go @@ -1,7 +1,6 @@ package keeper import ( - "fmt" "sort" "cosmossdk.io/math" @@ -52,35 +51,73 @@ func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, ga return result, nil } -// CalculateUniformPayouts returns payouts for the executors of the given reveals -// and the total gas used for the execution under the uniform reporting scenario. -func CalculateUniformPayouts(reveals []types.RevealBody, execGasLimit uint64, replicationFactor uint16, gasPrice string) ([]types.DistributionMessage, uint64, error) { - gasUsed := max(reveals[0].GasUsed, execGasLimit/uint64(replicationFactor)) - gasPriceInt, ok := math.NewIntFromString(gasPrice) - if !ok { - return nil, 0, fmt.Errorf("invalid gas price: %s", gasPrice) // TODO error +// CalculateUniformPayouts calculates payouts for the executors when their gas +// reports are uniformly at "gasUsed". It also returns the total execution gas +// consumption. +func CalculateUniformPayouts(executors []string, gasUsed, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.DistributionMessage, uint64) { + adjGasUsed := max(gasUsed, execGasLimit/uint64(replicationFactor)) + payout := gasPrice.Mul(math.NewIntFromUint64(adjGasUsed)) + + distMsgs := make([]types.DistributionMessage, len(executors)) + for i := range executors { + distMsgs[i] = types.DistributionMessage{ + Kind: types.DistributionKind{ + ExecutorReward: &types.DistributionExecutorReward{ + Identity: executors[i], + Amount: payout, + }, + }, + Type: types.DistributionTypeExecutorReward, + } } - payout := gasPriceInt.Mul(math.NewIntFromUint64(gasUsed)) + return distMsgs, adjGasUsed * uint64(replicationFactor) +} - distMsgs := make([]types.DistributionMessage, len(reveals)) +// CalculateDivergentPayouts calculates payouts for the executors of the given +// reveals when their gas reports are divergent. It also returns the total +// execution gas consumption. +// It assumes that the i-th executor is the one who revealed the i-th reveal. +func CalculateDivergentPayouts(executors []string, reveals []types.RevealBody, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.DistributionMessage, uint64) { + adjGasUsed := make([]uint64, len(reveals)) + var lowestGasUsed uint64 + var lowestReporterIndex int for i, reveal := range reveals { + adjGasUsed[i] = min(reveal.GasUsed, execGasLimit/uint64(replicationFactor)) + if i == 0 || adjGasUsed[i] < lowestGasUsed { + lowestReporterIndex = i + lowestGasUsed = adjGasUsed[i] + } + } + medianGasUsed := median(adjGasUsed) + totalGasUsed := medianGasUsed*uint64(replicationFactor-1) + min(lowestGasUsed*2, medianGasUsed) + totalShares := medianGasUsed*uint64(replicationFactor-1) + lowestGasUsed*2 + lowestPayout := gasPrice.Mul(math.NewIntFromUint64(lowestGasUsed * 2 * totalGasUsed / totalShares)) + normalPayout := gasPrice.Mul(math.NewIntFromUint64(medianGasUsed * totalGasUsed / totalShares)) + + distMsgs := make([]types.DistributionMessage, len(executors)) + for i, executor := range executors { + payout := normalPayout + if i == lowestReporterIndex { + payout = lowestPayout + } distMsgs[i] = types.DistributionMessage{ Kind: types.DistributionKind{ ExecutorReward: &types.DistributionExecutorReward{ - Identity: reveal.ID, + Identity: executor, Amount: payout, }, }, - Type: types.DistributionTypeTimedOut, // TODO check + Type: types.DistributionTypeExecutorReward, } } - return distMsgs, gasUsed, nil + return distMsgs, totalGasUsed } -// CalculateDivergentPayouts returns payouts for the executors of the given reveals -// and the total gas used for the execution under the divergent reporting scenario. -func CalculateDivergentPayouts(reveals []types.RevealBody, execGasLimit uint64, replicationFactor uint16, gasPrice string) ([]types.DistributionMessage, uint64, error) { - return nil, 0, nil +func median(arr []uint64) uint64 { + sort.Slice(arr, func(i, j int) bool { + return arr[i] < arr[j] + }) + return arr[len(arr)/2] } // areGasReportsUniform returns true if the gas reports of the given reveals are diff --git a/x/tally/keeper/tally_vm.go b/x/tally/keeper/tally_vm.go index 5c79baf8..ad87c417 100644 --- a/x/tally/keeper/tally_vm.go +++ b/x/tally/keeper/tally_vm.go @@ -15,6 +15,7 @@ import ( const ( TallyExitCodeNotEnoughCommits = 200 // tally VM not executed due to not enough commits + TallyExitCodeNoReveals = 201 // tally VM not executed due to no reveals TallyExitCodeInvalidFilterInput = 253 // tally VM not executed due to invalid filter input TallyExitCodeFilterError = 254 // tally VM not executed due to filter error TallyExitCodeExecError = 255 // error while executing tally VM From ad59ae692bae67de9117064c79aace1e1d566549 Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Tue, 14 Jan 2025 05:33:57 -0500 Subject: [PATCH 5/9] feat(x/tally): data provider payout --- app/app.go | 2 + x/batching/keeper/integration_test.go | 9 ++++ x/tally/keeper/endblock.go | 50 +++++++++++++----- x/tally/keeper/integration_test.go | 10 ++++ x/tally/keeper/keeper.go | 4 +- x/tally/keeper/payout.go | 76 ++++++++++++++++++--------- x/tally/types/abci_types.go | 14 +---- x/tally/types/expected_keepers.go | 5 ++ 8 files changed, 119 insertions(+), 51 deletions(-) diff --git a/app/app.go b/app/app.go index 80afb423..5f91cabd 100644 --- a/app/app.go +++ b/app/app.go @@ -135,6 +135,7 @@ import ( "github.com/sedaprotocol/seda-chain/app/keepers" appparams "github.com/sedaprotocol/seda-chain/app/params" "github.com/sedaprotocol/seda-chain/app/utils" + // Used in cosmos-sdk when registering the route for swagger docs. _ "github.com/sedaprotocol/seda-chain/client/docs/statik" "github.com/sedaprotocol/seda-chain/cmd/sedad/gentx" @@ -685,6 +686,7 @@ func NewApp( runtime.NewKVStoreService(keys[tallytypes.StoreKey]), app.WasmStorageKeeper, app.BatchingKeeper, + app.DataProxyKeeper, contractKeeper, app.WasmKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), diff --git a/x/batching/keeper/integration_test.go b/x/batching/keeper/integration_test.go index f65baf7d..b5067e85 100644 --- a/x/batching/keeper/integration_test.go +++ b/x/batching/keeper/integration_test.go @@ -48,6 +48,8 @@ import ( "github.com/sedaprotocol/seda-chain/x/batching" batchingkeeper "github.com/sedaprotocol/seda-chain/x/batching/keeper" "github.com/sedaprotocol/seda-chain/x/batching/types" + dataproxykeeper "github.com/sedaprotocol/seda-chain/x/data-proxy/keeper" + dataproxytypes "github.com/sedaprotocol/seda-chain/x/data-proxy/types" "github.com/sedaprotocol/seda-chain/x/pubkey" pubkeykeeper "github.com/sedaprotocol/seda-chain/x/pubkey/keeper" pubkeytypes "github.com/sedaprotocol/seda-chain/x/pubkey/types" @@ -221,6 +223,12 @@ func initFixture(tb testing.TB) *fixture { ) stakingKeeper.SetPubKeyKeeper(pubKeyKeeper) + dataProxyKeeper := dataproxykeeper.NewKeeper( + cdc, + runtime.NewKVStoreService(keys[dataproxytypes.StoreKey]), + authtypes.NewModuleAddress("gov").String(), + ) + batchingKeeper := batchingkeeper.NewKeeper( cdc, runtime.NewKVStoreService(keys[types.StoreKey]), @@ -238,6 +246,7 @@ func initFixture(tb testing.TB) *fixture { runtime.NewKVStoreService(keys[tallytypes.StoreKey]), wasmStorageKeeper, batchingKeeper, + dataProxyKeeper, contractKeeper, viewKeeper, authority.String(), diff --git a/x/tally/keeper/endblock.go b/x/tally/keeper/endblock.go index c9c2deb7..56ed1a36 100644 --- a/x/tally/keeper/endblock.go +++ b/x/tally/keeper/endblock.go @@ -127,6 +127,7 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err if !ok { return fmt.Errorf("invalid gas price: %s", req.GasPrice) // TODO improve error handling } + // TODO also make sure gas price is not 0 _, tallyResults[i], distMsgs = k.FilterAndTally(ctx, req, params, gasPriceInt) dataResults[i].Result = tallyResults[i].Result @@ -214,13 +215,13 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. sort.Strings(reveals[i].ProxyPubKeys) } - // Phase I: Filtering + // Phase 1: Filtering filterResult, filterErr := ExecuteFilter(reveals, req.ConsensusFilter, req.ReplicationFactor, params) result.Consensus = filterResult.Consensus result.ProxyPubKeys = filterResult.ProxyPubKeys result.TallyGasUsed += filterResult.GasUsed - // Phase II: Tally Program Execution + // Phase 2: Tally Program Execution if filterErr == nil { vmRes, err := k.ExecuteTallyProgram(ctx, req, filterResult, reveals) if err != nil { @@ -242,21 +243,44 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. } } - // Phase III: Calculate Payouts - var distMsgs types.DistributionMessages - if filterErr == nil || errors.Is(filterErr, types.ErrConsensusInError) { - var gasUsed uint64 - if req.ReplicationFactor == 1 || areGasReportsUniform(reveals) { - distMsgs.Messages, gasUsed = CalculateUniformPayouts(keys, reveals[0].GasUsed, req.ExecGasLimit, req.ReplicationFactor, gasPrice) + // Phase 3: Calculate Payouts + // Calculate data proxy payouts if basic consensus was reached. + var proxyDistMsgs types.DistributionMessages + var proxyGasUsedPerExec uint64 + if filterErr == nil || !errors.Is(filterErr, types.ErrNoBasicConsensus) { + var err error + proxyDistMsgs, proxyGasUsedPerExec, err = k.CalculateDataProxyPayouts(ctx, result.ProxyPubKeys, gasPrice) + if err != nil { + // TODO error handling + } + } + + // Calculate executor payouts. + var execDistMsgs types.DistributionMessages + if filterErr == nil || !errors.Is(filterErr, types.ErrNoBasicConsensus) { + gasReports := make([]uint64, len(reveals)) + for i, reveal := range reveals { + gasReports[i] = max(0, reveal.GasUsed-proxyGasUsedPerExec) + } + + var execGasUsed uint64 + if req.ReplicationFactor == 1 || areGasReportsUniform(gasReports) { + execDistMsgs.Messages, execGasUsed = CalculateUniformPayouts(keys, gasReports[0], req.ExecGasLimit, req.ReplicationFactor, gasPrice) } else { - distMsgs.Messages, gasUsed = CalculateDivergentPayouts(keys, reveals, req.ExecGasLimit, req.ReplicationFactor, gasPrice) + execDistMsgs.Messages, execGasUsed = CalculateDivergentPayouts(keys, gasReports, req.ExecGasLimit, req.ReplicationFactor, gasPrice) + } + result.ExecGasUsed = execGasUsed + } else { + var err error + execDistMsgs, err = k.CalculateCommitterPayouts(ctx, req, gasPrice) + if err != nil { + // TODO error handling } - distMsgs.RefundType = types.DistributionTypeExecutorReward // TODO double check - result.ExecGasUsed = gasUsed } - // TODO: else pay committers? - return filterResult, result, distMsgs + return filterResult, result, types.DistributionMessages{ + Messages: append(proxyDistMsgs.Messages, execDistMsgs.Messages...), + } } // logErrAndRet logs the base error along with the request ID for diff --git a/x/tally/keeper/integration_test.go b/x/tally/keeper/integration_test.go index 4c1b3138..74db1852 100644 --- a/x/tally/keeper/integration_test.go +++ b/x/tally/keeper/integration_test.go @@ -48,6 +48,8 @@ import ( "github.com/sedaprotocol/seda-chain/integration" batchingkeeper "github.com/sedaprotocol/seda-chain/x/batching/keeper" batchingtypes "github.com/sedaprotocol/seda-chain/x/batching/types" + dataproxykeeper "github.com/sedaprotocol/seda-chain/x/data-proxy/keeper" + dataproxytypes "github.com/sedaprotocol/seda-chain/x/data-proxy/types" pubkeykeeper "github.com/sedaprotocol/seda-chain/x/pubkey/keeper" pubkeytypes "github.com/sedaprotocol/seda-chain/x/pubkey/types" "github.com/sedaprotocol/seda-chain/x/staking" @@ -220,6 +222,12 @@ func initFixture(t testing.TB) *fixture { ) stakingKeeper.SetPubKeyKeeper(pubKeyKeeper) + dataProxyKeeper := dataproxykeeper.NewKeeper( + cdc, + runtime.NewKVStoreService(keys[dataproxytypes.StoreKey]), + authtypes.NewModuleAddress("gov").String(), + ) + batchingKeeper := batchingkeeper.NewKeeper( cdc, runtime.NewKVStoreService(keys[batchingtypes.StoreKey]), @@ -231,11 +239,13 @@ func initFixture(t testing.TB) *fixture { wasmKeeper, addresscodec.NewBech32Codec(params.Bech32PrefixValAddr), ) + tallyKeeper := keeper.NewKeeper( cdc, runtime.NewKVStoreService(keys[types.StoreKey]), wasmStorageKeeper, batchingKeeper, + dataProxyKeeper, contractKeeper, wasmKeeper, authority.String(), diff --git a/x/tally/keeper/keeper.go b/x/tally/keeper/keeper.go index d7cbb89a..9dcabd26 100644 --- a/x/tally/keeper/keeper.go +++ b/x/tally/keeper/keeper.go @@ -18,6 +18,7 @@ import ( type Keeper struct { wasmStorageKeeper types.WasmStorageKeeper batchingKeeper types.BatchingKeeper + dataProxyKeeper types.DataProxyKeeper wasmKeeper wasmtypes.ContractOpsKeeper wasmViewKeeper wasmtypes.ViewKeeper authority string @@ -26,12 +27,13 @@ type Keeper struct { params collections.Item[types.Params] } -func NewKeeper(cdc codec.BinaryCodec, storeService storetypes.KVStoreService, wsk types.WasmStorageKeeper, bk types.BatchingKeeper, wk wasmtypes.ContractOpsKeeper, wvk wasmtypes.ViewKeeper, authority string) Keeper { +func NewKeeper(cdc codec.BinaryCodec, storeService storetypes.KVStoreService, wsk types.WasmStorageKeeper, bk types.BatchingKeeper, dpk types.DataProxyKeeper, wk wasmtypes.ContractOpsKeeper, wvk wasmtypes.ViewKeeper, authority string) Keeper { sb := collections.NewSchemaBuilder(storeService) k := Keeper{ wasmStorageKeeper: wsk, batchingKeeper: bk, + dataProxyKeeper: dpk, wasmKeeper: wk, wasmViewKeeper: wvk, params: collections.NewItem(sb, types.ParamsPrefix, "params", codec.CollValue[types.Params](cdc)), diff --git a/x/tally/keeper/payout.go b/x/tally/keeper/payout.go index 1501fd16..0e2fc893 100644 --- a/x/tally/keeper/payout.go +++ b/x/tally/keeper/payout.go @@ -1,6 +1,7 @@ package keeper import ( + "encoding/hex" "sort" "cosmossdk.io/math" @@ -10,13 +11,44 @@ import ( "github.com/sedaprotocol/seda-chain/x/tally/types" ) -// CalculateCommitterPayouts constructs distribution messages that -// pay the fixed gas cost for each commiter of a given data request. -func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, gasPrice math.Int) (types.DistributionMessages, error) { - result := types.DistributionMessages{ - Messages: []types.DistributionMessage{}, - RefundType: types.DistributionTypeTimedOut, +// CalculateDataProxyPayouts returns payouts for the data proxies and +// returns the the gas used by the data proxies per executor. +func (k Keeper) CalculateDataProxyPayouts(ctx sdk.Context, proxyPubKeys []string, gasPrice math.Int) (types.DistributionMessages, uint64, error) { + var result types.DistributionMessages + if len(proxyPubKeys) == 0 { + return result, 0, nil + } + + gasUsed := math.NewInt(0) + distMsgs := make([]types.DistributionMessage, len(proxyPubKeys)) + for i, pubKey := range proxyPubKeys { + pubKeyBytes, err := hex.DecodeString(pubKey) + if err != nil { + return types.DistributionMessages{}, 0, err + } + proxyConfig, err := k.dataProxyKeeper.GetDataProxyConfig(ctx, pubKeyBytes) + if err != nil { + return types.DistributionMessages{}, 0, err + } + gasUsed = gasUsed.Add(proxyConfig.Fee.Amount.Quo(gasPrice)) + + distMsgs[i] = types.DistributionMessage{ + Kind: types.DistributionKind{ + ExecutorReward: &types.DistributionExecutorReward{ + Identity: pubKey, + Amount: proxyConfig.Fee.Amount, + }, + }, + } } + result.Messages = distMsgs + return types.DistributionMessages{}, gasUsed.Uint64(), nil // TODO may panic +} + +// CalculateCommitterPayouts returns the fixed payouts for the committers of a +// given data request. +func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, gasPrice math.Int) (types.DistributionMessages, error) { + var result types.DistributionMessages if len(req.Commits) == 0 { return result, nil } @@ -44,7 +76,6 @@ func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, ga Amount: payout, }, }, - Type: types.DistributionTypeTimedOut, } } result.Messages = distMsgs @@ -52,10 +83,10 @@ func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, ga } // CalculateUniformPayouts calculates payouts for the executors when their gas -// reports are uniformly at "gasUsed". It also returns the total execution gas +// reports are uniformly at "gasReport". It also returns the total execution gas // consumption. -func CalculateUniformPayouts(executors []string, gasUsed, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.DistributionMessage, uint64) { - adjGasUsed := max(gasUsed, execGasLimit/uint64(replicationFactor)) +func CalculateUniformPayouts(executors []string, gasReport, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.DistributionMessage, uint64) { + adjGasUsed := max(gasReport, execGasLimit/uint64(replicationFactor)) payout := gasPrice.Mul(math.NewIntFromUint64(adjGasUsed)) distMsgs := make([]types.DistributionMessage, len(executors)) @@ -67,22 +98,20 @@ func CalculateUniformPayouts(executors []string, gasUsed, execGasLimit uint64, r Amount: payout, }, }, - Type: types.DistributionTypeExecutorReward, } } return distMsgs, adjGasUsed * uint64(replicationFactor) } -// CalculateDivergentPayouts calculates payouts for the executors of the given -// reveals when their gas reports are divergent. It also returns the total -// execution gas consumption. +// CalculateDivergentPayouts calculates payouts for the executors given their +// divergent gas reports. It also returns the total execution gas consumption. // It assumes that the i-th executor is the one who revealed the i-th reveal. -func CalculateDivergentPayouts(executors []string, reveals []types.RevealBody, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.DistributionMessage, uint64) { - adjGasUsed := make([]uint64, len(reveals)) +func CalculateDivergentPayouts(executors []string, gasReports []uint64, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.DistributionMessage, uint64) { + adjGasUsed := make([]uint64, len(gasReports)) var lowestGasUsed uint64 var lowestReporterIndex int - for i, reveal := range reveals { - adjGasUsed[i] = min(reveal.GasUsed, execGasLimit/uint64(replicationFactor)) + for i, gasReport := range gasReports { + adjGasUsed[i] = min(gasReport, execGasLimit/uint64(replicationFactor)) if i == 0 || adjGasUsed[i] < lowestGasUsed { lowestReporterIndex = i lowestGasUsed = adjGasUsed[i] @@ -107,7 +136,6 @@ func CalculateDivergentPayouts(executors []string, reveals []types.RevealBody, e Amount: payout, }, }, - Type: types.DistributionTypeExecutorReward, } } return distMsgs, totalGasUsed @@ -122,13 +150,13 @@ func median(arr []uint64) uint64 { // areGasReportsUniform returns true if the gas reports of the given reveals are // uniform. -func areGasReportsUniform(reveals []types.RevealBody) bool { - if len(reveals) == 0 { +func areGasReportsUniform(reports []uint64) bool { + if len(reports) == 0 { return true } - firstGas := reveals[0].GasUsed - for i := 1; i < len(reveals); i++ { - if reveals[i].GasUsed != firstGas { + firstGas := reports[0] + for i := 1; i < len(reports); i++ { + if reports[i] != firstGas { return false } } diff --git a/x/tally/types/abci_types.go b/x/tally/types/abci_types.go index aec3a753..62fbb8a2 100644 --- a/x/tally/types/abci_types.go +++ b/x/tally/types/abci_types.go @@ -106,13 +106,11 @@ func (u *RevealBody) TryHash() (string, error) { } type DistributionMessages struct { - Messages []DistributionMessage `json:"messages"` - RefundType DistributionType `json:"refund_type"` + Messages []DistributionMessage `json:"messages"` } type DistributionMessage struct { Kind DistributionKind `json:"kind"` - Type DistributionType `json:"type"` } type DistributionKind struct { @@ -135,16 +133,6 @@ type DistributionExecutorReward struct { Identity string `json:"identity"` } -type DistributionType string - -const ( - DistributionTypeTallyReward DistributionType = "tally_reward" - DistributionTypeExecutorReward DistributionType = "executor_reward" - DistributionTypeTimedOut DistributionType = "timed_out" - DistributionTypeNoConsensus DistributionType = "no_consensus" - DistributionTypeRemainderRefund DistributionType = "remainder_refund" -) - func MarshalSudoRemoveDataRequests(processedReqs map[string]DistributionMessages) ([]byte, error) { return json.Marshal(struct { SudoRemoveDataRequests struct { diff --git a/x/tally/types/expected_keepers.go b/x/tally/types/expected_keepers.go index b279701e..e7838af9 100644 --- a/x/tally/types/expected_keepers.go +++ b/x/tally/types/expected_keepers.go @@ -4,8 +4,13 @@ import ( "context" batchingtypes "github.com/sedaprotocol/seda-chain/x/batching/types" + dataproxytypes "github.com/sedaprotocol/seda-chain/x/data-proxy/types" ) type BatchingKeeper interface { SetDataResultForBatching(ctx context.Context, result batchingtypes.DataResult) error } + +type DataProxyKeeper interface { + GetDataProxyConfig(ctx context.Context, pubKey []byte) (result dataproxytypes.ProxyConfig, err error) +} From 099eed5077f65972ae99fa442764cbeb51997e5e Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Wed, 15 Jan 2025 14:58:31 -0500 Subject: [PATCH 6/9] chore(x/tally): check for basic consensus before building filter --- x/tally/keeper/filter.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x/tally/keeper/filter.go b/x/tally/keeper/filter.go index 8ec953ed..995d8c90 100644 --- a/x/tally/keeper/filter.go +++ b/x/tally/keeper/filter.go @@ -46,15 +46,9 @@ func invertErrors(errors []bool) []bool { // outliers. It assumes that the reveals are sorted by their keys and that their // proxy public keys are sorted. func ExecuteFilter(reveals []types.RevealBody, filterInput string, replicationFactor uint16, params types.Params) (FilterResult, error) { - filter, err := BuildFilter(filterInput, replicationFactor, params) - if err != nil { - return FilterResult{}, types.ErrInvalidFilterInput.Wrap(err.Error()) - } - var result FilterResult result.Errors = make([]bool, len(reveals)) result.Outliers = make([]bool, len(reveals)) - result.GasUsed = filter.GasCost() // Determine basic consensus on tuple of (exit_code_success, proxy_pub_keys) var maxFreq int @@ -75,6 +69,12 @@ func ExecuteFilter(reveals []types.RevealBody, filterInput string, replicationFa return result, types.ErrNoBasicConsensus } + filter, err := BuildFilter(filterInput, replicationFactor, params) + if err != nil { + return result, types.ErrInvalidFilterInput.Wrap(err.Error()) + } + result.GasUsed = filter.GasCost() + outliers, consensus := filter.ApplyFilter(reveals, result.Errors) switch { case countErrors(result.Errors)*3 > len(reveals)*2: From eefd20655182723ef7333f883c236021ecae8722 Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Wed, 15 Jan 2025 20:15:15 -0500 Subject: [PATCH 7/9] chore(x/tally): add base fee and update distribution type --- app/app.go | 1 - proto/sedachain/tally/v1/tally.proto | 7 +- x/tally/keeper/endblock.go | 59 ++++++------ x/tally/keeper/filter_and_tally_test.go | 25 +++--- x/tally/keeper/keeper.go | 8 -- x/tally/keeper/payout.go | 85 +++++------------- x/tally/keeper/testdata/core_contract.wasm | Bin 642690 -> 629976 bytes x/tally/types/abci_types.go | 48 ++++++---- x/tally/types/params.go | 13 ++- x/tally/types/tally.pb.go | 100 ++++++++++++++------- 10 files changed, 185 insertions(+), 161 deletions(-) diff --git a/app/app.go b/app/app.go index 5f91cabd..6da8975d 100644 --- a/app/app.go +++ b/app/app.go @@ -135,7 +135,6 @@ import ( "github.com/sedaprotocol/seda-chain/app/keepers" appparams "github.com/sedaprotocol/seda-chain/app/params" "github.com/sedaprotocol/seda-chain/app/utils" - // Used in cosmos-sdk when registering the route for swagger docs. _ "github.com/sedaprotocol/seda-chain/client/docs/statik" "github.com/sedaprotocol/seda-chain/cmd/sedad/gentx" diff --git a/proto/sedachain/tally/v1/tally.proto b/proto/sedachain/tally/v1/tally.proto index b51899a2..a52345d9 100644 --- a/proto/sedachain/tally/v1/tally.proto +++ b/proto/sedachain/tally/v1/tally.proto @@ -15,7 +15,8 @@ message Params { // FilterGasCostMultiplierStdDev is the gas cost multiplier for a filter type // stddev. uint64 filter_gas_cost_multiplier_std_dev = 4; - // GasCostCommitment is the gas cost for a commitment corresponding to an - // expired data request. - uint64 gas_cost_commitment = 5; + // GasCostBase is the base gas cost for a data request. + uint64 gas_cost_base = 5; + // GasCostCommit is the gas cost for a commit charged under certain scenarios. + uint64 gas_cost_commit = 6; } diff --git a/x/tally/keeper/endblock.go b/x/tally/keeper/endblock.go index 56ed1a36..2adcabf0 100644 --- a/x/tally/keeper/endblock.go +++ b/x/tally/keeper/endblock.go @@ -9,6 +9,7 @@ import ( "strings" "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sedaprotocol/seda-wasm-vm/tallyvm/v2" @@ -81,7 +82,7 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err // Loop through the list to apply filter, execute tally, and post // execution result. - processedReqs := make(map[string]types.DistributionMessages) + processedReqs := make(map[string][]types.Distribution) tallyResults := make([]TallyResult, len(tallyList)) dataResults := make([]batchingtypes.DataResult, len(tallyList)) for i, req := range tallyList { @@ -97,39 +98,36 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err SedaPayload: req.SedaPayload, } - gasPriceInt, ok := math.NewIntFromString(req.GasPrice) + gasPrice, ok := math.NewIntFromString(req.GasPrice) if !ok { - return fmt.Errorf("invalid gas price: %s", req.GasPrice) // TODO improve error handling + return fmt.Errorf("invalid gas price: %s", req.GasPrice) } - var distMsgs types.DistributionMessages + var dists []types.Distribution switch { case len(req.Commits) < int(req.ReplicationFactor): dataResults[i].Result = []byte(fmt.Sprintf("need %d commits; received %d", req.ReplicationFactor, len(req.Commits))) dataResults[i].ExitCode = TallyExitCodeNotEnoughCommits k.Logger(ctx).Info("data request's number of commits did not meet replication factor", "request_id", req.ID) - distMsgs, err = k.CalculateCommitterPayouts(ctx, req, gasPriceInt) + dists, err = CalculateCommitterPayouts(req, gasPrice.Mul(math.NewIntFromUint64(params.GasCostCommit))) if err != nil { return err } case len(req.Reveals) == 0: - dataResults[i].Result = []byte(fmt.Sprintf("no reveals")) + dataResults[i].Result = []byte("no reveals") dataResults[i].ExitCode = TallyExitCodeNoReveals k.Logger(ctx).Info("data request has no reveals", "request_id", req.ID) - distMsgs, err = k.CalculateCommitterPayouts(ctx, req, gasPriceInt) + dists, err = CalculateCommitterPayouts(req, gasPrice.Mul(math.NewIntFromUint64(params.GasCostCommit))) if err != nil { return err } default: - gasPriceInt, ok := math.NewIntFromString(req.GasPrice) - if !ok { - return fmt.Errorf("invalid gas price: %s", req.GasPrice) // TODO improve error handling + _, tallyResults[i], dists, err = k.FilterAndTally(ctx, req, params, gasPrice) + if err != nil { + return err } - // TODO also make sure gas price is not 0 - - _, tallyResults[i], distMsgs = k.FilterAndTally(ctx, req, params, gasPriceInt) dataResults[i].Result = tallyResults[i].Result //nolint:gosec // G115: We shouldn't get negative exit code anyway. dataResults[i].ExitCode = uint32(tallyResults[i].ExitInfo.ExitCode) @@ -140,7 +138,12 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err k.Logger(ctx).Debug("tally result", "request_id", req.ID, "tally_result", tallyResults[i]) } - processedReqs[req.ID] = distMsgs + baseFeeMsg := types.Distribution{ + Burn: &types.DistributionBurn{ + Amount: gasPrice.Mul(math.NewIntFromUint64(params.GasCostBase)), + }, + } + processedReqs[req.ID] = append([]types.Distribution{baseFeeMsg}, dists...) dataResults[i].Id, err = dataResults[i].TryHash() if err != nil { return err @@ -197,8 +200,9 @@ type TallyResult struct { // FilterAndTally builds and applies filter, executes tally program, // and calculates payouts. -func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types.Params, gasPrice math.Int) (FilterResult, TallyResult, types.DistributionMessages) { +func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types.Params, gasPrice math.Int) (FilterResult, TallyResult, []types.Distribution, error) { var result TallyResult + var dists []types.Distribution // Sort the reveals by their keys (executors). keys := make([]string, len(req.Reveals)) @@ -233,7 +237,10 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. result.StdOut = vmRes.Stdout result.StdErr = vmRes.Stderr } - result.TallyGasUsed += vmRes.GasUsed + if vmRes.GasUsed > 0 { + result.TallyGasUsed += vmRes.GasUsed + dists = append(dists, types.NewBurn(gasPrice.Mul(math.NewIntFromUint64(vmRes.GasUsed)))) + } } else { result.Result = []byte(filterErr.Error()) if errors.Is(filterErr, types.ErrInvalidFilterInput) { @@ -245,18 +252,19 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. // Phase 3: Calculate Payouts // Calculate data proxy payouts if basic consensus was reached. - var proxyDistMsgs types.DistributionMessages + var proxyDistMsgs []types.Distribution var proxyGasUsedPerExec uint64 if filterErr == nil || !errors.Is(filterErr, types.ErrNoBasicConsensus) { var err error proxyDistMsgs, proxyGasUsedPerExec, err = k.CalculateDataProxyPayouts(ctx, result.ProxyPubKeys, gasPrice) if err != nil { - // TODO error handling + return filterResult, result, dists, err } } + dists = append(dists, proxyDistMsgs...) // Calculate executor payouts. - var execDistMsgs types.DistributionMessages + var execDistMsgs []types.Distribution if filterErr == nil || !errors.Is(filterErr, types.ErrNoBasicConsensus) { gasReports := make([]uint64, len(reveals)) for i, reveal := range reveals { @@ -265,22 +273,21 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. var execGasUsed uint64 if req.ReplicationFactor == 1 || areGasReportsUniform(gasReports) { - execDistMsgs.Messages, execGasUsed = CalculateUniformPayouts(keys, gasReports[0], req.ExecGasLimit, req.ReplicationFactor, gasPrice) + execDistMsgs, execGasUsed = CalculateUniformPayouts(keys, gasReports[0], req.ExecGasLimit, req.ReplicationFactor, gasPrice) } else { - execDistMsgs.Messages, execGasUsed = CalculateDivergentPayouts(keys, gasReports, req.ExecGasLimit, req.ReplicationFactor, gasPrice) + execDistMsgs, execGasUsed = CalculateDivergentPayouts(keys, gasReports, req.ExecGasLimit, req.ReplicationFactor, gasPrice) } result.ExecGasUsed = execGasUsed } else { var err error - execDistMsgs, err = k.CalculateCommitterPayouts(ctx, req, gasPrice) + execDistMsgs, err = CalculateCommitterPayouts(req, gasPrice.Mul(math.NewIntFromUint64(params.GasCostCommit))) if err != nil { - // TODO error handling + return filterResult, result, dists, err } } + dists = append(dists, execDistMsgs...) - return filterResult, result, types.DistributionMessages{ - Messages: append(proxyDistMsgs.Messages, execDistMsgs.Messages...), - } + return filterResult, result, dists, nil } // logErrAndRet logs the base error along with the request ID for diff --git a/x/tally/keeper/filter_and_tally_test.go b/x/tally/keeper/filter_and_tally_test.go index 330305ee..e0e45f69 100644 --- a/x/tally/keeper/filter_and_tally_test.go +++ b/x/tally/keeper/filter_and_tally_test.go @@ -58,7 +58,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - filterGasUsed: defaultParams.FilterGasCostNone, + filterGasUsed: 0, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -89,7 +89,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - filterGasUsed: defaultParams.FilterGasCostMultiplierMode * 5, + filterGasUsed: 0, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -120,7 +120,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - filterGasUsed: defaultParams.FilterGasCostMultiplierStdDev * 5, + filterGasUsed: 0, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -138,13 +138,14 @@ func TestFilterAndTally(t *testing.T) { reveals[k] = revealBody } - filterRes, tallyRes, _ := f.tallyKeeper.FilterAndTally(f.Context(), types.Request{ + filterRes, tallyRes, _, err := f.tallyKeeper.FilterAndTally(f.Context(), types.Request{ Reveals: reveals, ReplicationFactor: tt.replicationFactor, ConsensusFilter: base64.StdEncoding.EncodeToString(filterInput), GasPrice: "1000000000000000000", // 1e18 ExecGasLimit: 100000, }, types.DefaultParams(), math.NewInt(1000000000000000000)) + require.NoError(t, err) require.Equal(t, tt.outliers, filterRes.Outliers) require.Equal(t, tt.filterGasUsed, filterRes.GasUsed) @@ -247,18 +248,22 @@ func TestExecutorPayout(t *testing.T) { reveals[k] = revealBody } - _, tallyRes, distMsgs := f.tallyKeeper.FilterAndTally(f.Context(), types.Request{ + gasPriceStr := "1000000000000000000" // 1e18 + gasPrice, ok := math.NewIntFromString(gasPriceStr) + require.True(t, ok) + + _, tallyRes, distMsgs, err := f.tallyKeeper.FilterAndTally(f.Context(), types.Request{ Reveals: reveals, ReplicationFactor: tt.replicationFactor, ConsensusFilter: base64.StdEncoding.EncodeToString(filterInput), - GasPrice: "1000000000000000000", // 1e18 + GasPrice: gasPriceStr, ExecGasLimit: tt.execGasLimit, - }, types.DefaultParams(), math.NewInt(1000000000000000000)) + }, types.DefaultParams(), gasPrice) + require.NoError(t, err) require.Equal(t, tt.expExecGasUsed, tallyRes.ExecGasUsed) - for _, distMsg := range distMsgs.Messages { - require.Equal(t, tt.expExecutorRewards[distMsg.Kind.ExecutorReward.Identity], distMsg.Kind.ExecutorReward.Amount) - require.Equal(t, types.DistributionTypeExecutorReward, distMsg.Type) + for _, distMsg := range distMsgs { + require.Equal(t, tt.expExecutorRewards[distMsg.ExecutorReward.Identity], distMsg.ExecutorReward.Amount) } }) } diff --git a/x/tally/keeper/keeper.go b/x/tally/keeper/keeper.go index 9dcabd26..e654ab54 100644 --- a/x/tally/keeper/keeper.go +++ b/x/tally/keeper/keeper.go @@ -63,14 +63,6 @@ func (k Keeper) GetMaxTallyGasLimit(ctx sdk.Context) (uint64, error) { return params.MaxTallyGasLimit, nil } -func (k Keeper) GetGasCostCommitment(ctx sdk.Context) (uint64, error) { - params, err := k.params.Get(ctx) - if err != nil { - return 0, err - } - return params.GasCostCommitment, nil -} - func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } diff --git a/x/tally/keeper/payout.go b/x/tally/keeper/payout.go index 0e2fc893..346741fb 100644 --- a/x/tally/keeper/payout.go +++ b/x/tally/keeper/payout.go @@ -13,51 +13,36 @@ import ( // CalculateDataProxyPayouts returns payouts for the data proxies and // returns the the gas used by the data proxies per executor. -func (k Keeper) CalculateDataProxyPayouts(ctx sdk.Context, proxyPubKeys []string, gasPrice math.Int) (types.DistributionMessages, uint64, error) { - var result types.DistributionMessages +func (k Keeper) CalculateDataProxyPayouts(ctx sdk.Context, proxyPubKeys []string, gasPrice math.Int) ([]types.Distribution, uint64, error) { if len(proxyPubKeys) == 0 { - return result, 0, nil + return nil, 0, nil } gasUsed := math.NewInt(0) - distMsgs := make([]types.DistributionMessage, len(proxyPubKeys)) + dists := make([]types.Distribution, len(proxyPubKeys)) for i, pubKey := range proxyPubKeys { pubKeyBytes, err := hex.DecodeString(pubKey) if err != nil { - return types.DistributionMessages{}, 0, err + return nil, 0, err } proxyConfig, err := k.dataProxyKeeper.GetDataProxyConfig(ctx, pubKeyBytes) if err != nil { - return types.DistributionMessages{}, 0, err + return nil, 0, err } gasUsed = gasUsed.Add(proxyConfig.Fee.Amount.Quo(gasPrice)) - distMsgs[i] = types.DistributionMessage{ - Kind: types.DistributionKind{ - ExecutorReward: &types.DistributionExecutorReward{ - Identity: pubKey, - Amount: proxyConfig.Fee.Amount, - }, - }, - } + dists[i] = types.NewDataProxyReward(pubKeyBytes, proxyConfig.Fee.Amount) } - result.Messages = distMsgs - return types.DistributionMessages{}, gasUsed.Uint64(), nil // TODO may panic + return dists, gasUsed.Uint64(), nil } -// CalculateCommitterPayouts returns the fixed payouts for the committers of a -// given data request. -func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, gasPrice math.Int) (types.DistributionMessages, error) { - var result types.DistributionMessages +// CalculateCommitterPayouts returns distribution messages of a given payout +// amount to the committers of a data request. The messages are sorted by +// committer public key. +func CalculateCommitterPayouts(req types.Request, payout math.Int) ([]types.Distribution, error) { if len(req.Commits) == 0 { - return result, nil - } - - gasCost, err := k.GetGasCostCommitment(ctx) - if err != nil { - return types.DistributionMessages{}, err + return nil, nil } - payout := gasPrice.Mul(math.NewIntFromUint64(gasCost)) i := 0 committers := make([]string, len(req.Commits)) @@ -67,46 +52,31 @@ func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, ga } sort.Strings(committers) - distMsgs := make([]types.DistributionMessage, len(committers)) + dists := make([]types.Distribution, len(committers)) for i, committer := range committers { - distMsgs[i] = types.DistributionMessage{ - Kind: types.DistributionKind{ - ExecutorReward: &types.DistributionExecutorReward{ - Identity: committer, - Amount: payout, - }, - }, - } + dists[i] = types.NewExecutorReward(committer, payout) } - result.Messages = distMsgs - return result, nil + return dists, nil } // CalculateUniformPayouts calculates payouts for the executors when their gas // reports are uniformly at "gasReport". It also returns the total execution gas // consumption. -func CalculateUniformPayouts(executors []string, gasReport, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.DistributionMessage, uint64) { +func CalculateUniformPayouts(executors []string, gasReport, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.Distribution, uint64) { adjGasUsed := max(gasReport, execGasLimit/uint64(replicationFactor)) payout := gasPrice.Mul(math.NewIntFromUint64(adjGasUsed)) - distMsgs := make([]types.DistributionMessage, len(executors)) - for i := range executors { - distMsgs[i] = types.DistributionMessage{ - Kind: types.DistributionKind{ - ExecutorReward: &types.DistributionExecutorReward{ - Identity: executors[i], - Amount: payout, - }, - }, - } + dists := make([]types.Distribution, len(executors)) + for i, executor := range executors { + dists[i] = types.NewExecutorReward(executor, payout) } - return distMsgs, adjGasUsed * uint64(replicationFactor) + return dists, adjGasUsed * uint64(replicationFactor) } // CalculateDivergentPayouts calculates payouts for the executors given their // divergent gas reports. It also returns the total execution gas consumption. // It assumes that the i-th executor is the one who revealed the i-th reveal. -func CalculateDivergentPayouts(executors []string, gasReports []uint64, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.DistributionMessage, uint64) { +func CalculateDivergentPayouts(executors []string, gasReports []uint64, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.Distribution, uint64) { adjGasUsed := make([]uint64, len(gasReports)) var lowestGasUsed uint64 var lowestReporterIndex int @@ -123,22 +93,15 @@ func CalculateDivergentPayouts(executors []string, gasReports []uint64, execGasL lowestPayout := gasPrice.Mul(math.NewIntFromUint64(lowestGasUsed * 2 * totalGasUsed / totalShares)) normalPayout := gasPrice.Mul(math.NewIntFromUint64(medianGasUsed * totalGasUsed / totalShares)) - distMsgs := make([]types.DistributionMessage, len(executors)) + dists := make([]types.Distribution, len(executors)) for i, executor := range executors { payout := normalPayout if i == lowestReporterIndex { payout = lowestPayout } - distMsgs[i] = types.DistributionMessage{ - Kind: types.DistributionKind{ - ExecutorReward: &types.DistributionExecutorReward{ - Identity: executor, - Amount: payout, - }, - }, - } + dists[i] = types.NewExecutorReward(executor, payout) } - return distMsgs, totalGasUsed + return dists, totalGasUsed } func median(arr []uint64) uint64 { diff --git a/x/tally/keeper/testdata/core_contract.wasm b/x/tally/keeper/testdata/core_contract.wasm index 356fdab13cbf3f4dbcfe353f987252d89f75a8d3..51adbd08c2c8c94c203e007987bcf06ec6ed074c 100644 GIT binary patch delta 150261 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

f>7!ihFJuXlZGFF0$ENo4``XqkpmVEcp0Ey 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 64 { + return ErrIntOverflowTally + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.GasCostBase |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field GasCostCommit", wireType) } - m.GasCostCommitment = 0 + m.GasCostCommit = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTally @@ -344,7 +380,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.GasCostCommitment |= uint64(b&0x7F) << shift + m.GasCostCommit |= uint64(b&0x7F) << shift if b < 0x80 { break } From dc86eb7ef915c3ce47e2b50cab6410c91980e7fd Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Thu, 16 Jan 2025 14:01:39 -0500 Subject: [PATCH 8/9] refactor(x/tally): improve payout accounting and error handling --- x/tally/keeper/endblock.go | 108 ++++++++------- x/tally/keeper/filter_and_tally_test.go | 172 +++++++++++++++++++++--- x/tally/keeper/filter_test.go | 6 +- x/tally/keeper/integration_test.go | 3 + x/tally/keeper/payout.go | 78 ++++++++--- x/tally/types/abci_types.go | 20 +++ x/tally/types/events.go | 10 +- 7 files changed, 309 insertions(+), 88 deletions(-) diff --git a/x/tally/keeper/endblock.go b/x/tally/keeper/endblock.go index 2adcabf0..24f4c0b1 100644 --- a/x/tally/keeper/endblock.go +++ b/x/tally/keeper/endblock.go @@ -103,31 +103,20 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err return fmt.Errorf("invalid gas price: %s", req.GasPrice) } - var dists []types.Distribution + var record types.PayoutRecord switch { case len(req.Commits) < int(req.ReplicationFactor): dataResults[i].Result = []byte(fmt.Sprintf("need %d commits; received %d", req.ReplicationFactor, len(req.Commits))) dataResults[i].ExitCode = TallyExitCodeNotEnoughCommits k.Logger(ctx).Info("data request's number of commits did not meet replication factor", "request_id", req.ID) - - dists, err = CalculateCommitterPayouts(req, gasPrice.Mul(math.NewIntFromUint64(params.GasCostCommit))) - if err != nil { - return err - } + record.ExecDists = k.CalculateCommitterPayouts(ctx, req, params.GasCostCommit, gasPrice) case len(req.Reveals) == 0: dataResults[i].Result = []byte("no reveals") dataResults[i].ExitCode = TallyExitCodeNoReveals k.Logger(ctx).Info("data request has no reveals", "request_id", req.ID) - - dists, err = CalculateCommitterPayouts(req, gasPrice.Mul(math.NewIntFromUint64(params.GasCostCommit))) - if err != nil { - return err - } + record.ExecDists = k.CalculateCommitterPayouts(ctx, req, params.GasCostCommit, gasPrice) default: - _, tallyResults[i], dists, err = k.FilterAndTally(ctx, req, params, gasPrice) - if err != nil { - return err - } + _, tallyResults[i], record = k.FilterAndTally(ctx, req, params, gasPrice) dataResults[i].Result = tallyResults[i].Result //nolint:gosec // G115: We shouldn't get negative exit code anyway. dataResults[i].ExitCode = uint32(tallyResults[i].ExitInfo.ExitCode) @@ -138,12 +127,13 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err k.Logger(ctx).Debug("tally result", "request_id", req.ID, "tally_result", tallyResults[i]) } - baseFeeMsg := types.Distribution{ - Burn: &types.DistributionBurn{ - Amount: gasPrice.Mul(math.NewIntFromUint64(params.GasCostBase)), - }, - } - processedReqs[req.ID] = append([]types.Distribution{baseFeeMsg}, dists...) + baseFee := gasPrice.Mul(math.NewIntFromUint64(params.GasCostBase)) + processedReqs[req.ID] = record.DistributionsWithBaseFee(baseFee) + + ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeBaseFeeBurn, + sdk.NewAttribute(sdk.AttributeKeyAmount, baseFee.String()), + )) + dataResults[i].Id, err = dataResults[i].TryHash() if err != nil { return err @@ -200,10 +190,7 @@ type TallyResult struct { // FilterAndTally builds and applies filter, executes tally program, // and calculates payouts. -func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types.Params, gasPrice math.Int) (FilterResult, TallyResult, []types.Distribution, error) { - var result TallyResult - var dists []types.Distribution - +func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types.Params, gasPrice math.Int) (FilterResult, TallyResult, types.PayoutRecord) { // Sort the reveals by their keys (executors). keys := make([]string, len(req.Reveals)) i := 0 @@ -221,15 +208,27 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. // Phase 1: Filtering filterResult, filterErr := ExecuteFilter(reveals, req.ConsensusFilter, req.ReplicationFactor, params) - result.Consensus = filterResult.Consensus - result.ProxyPubKeys = filterResult.ProxyPubKeys - result.TallyGasUsed += filterResult.GasUsed + // Begin tracking tally result and burn amount. + result := TallyResult{ + Consensus: filterResult.Consensus, + ProxyPubKeys: filterResult.ProxyPubKeys, + TallyGasUsed: filterResult.GasUsed, + } + burnAmt := math.ZeroInt() + if filterResult.GasUsed > 0 { + burnAmt = gasPrice.Mul(math.NewIntFromUint64(filterResult.GasUsed)) + ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeTallyGasBurn, + sdk.NewAttribute(sdk.AttributeKeyAmount, burnAmt.String()), + )) + } // Phase 2: Tally Program Execution + var vmRes tallyvm.VmResult + var tallyErr error if filterErr == nil { - vmRes, err := k.ExecuteTallyProgram(ctx, req, filterResult, reveals) - if err != nil { - result.Result = []byte(err.Error()) + vmRes, tallyErr = k.ExecuteTallyProgram(ctx, req, filterResult, reveals) + if tallyErr != nil { + result.Result = []byte(tallyErr.Error()) result.ExitInfo.ExitCode = TallyExitCodeExecError } else { result.Result = vmRes.Result @@ -239,7 +238,12 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. } if vmRes.GasUsed > 0 { result.TallyGasUsed += vmRes.GasUsed - dists = append(dists, types.NewBurn(gasPrice.Mul(math.NewIntFromUint64(vmRes.GasUsed)))) + + gasFee := gasPrice.Mul(math.NewIntFromUint64(vmRes.GasUsed)) + burnAmt = burnAmt.Add(gasFee) + ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeTallyGasBurn, + sdk.NewAttribute(sdk.AttributeKeyAmount, gasFee.String()), + )) } } else { result.Result = []byte(filterErr.Error()) @@ -255,39 +259,43 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. var proxyDistMsgs []types.Distribution var proxyGasUsedPerExec uint64 if filterErr == nil || !errors.Is(filterErr, types.ErrNoBasicConsensus) { - var err error - proxyDistMsgs, proxyGasUsedPerExec, err = k.CalculateDataProxyPayouts(ctx, result.ProxyPubKeys, gasPrice) - if err != nil { - return filterResult, result, dists, err - } + proxyDistMsgs, proxyGasUsedPerExec = k.CalculateDataProxyPayouts(ctx, result.ProxyPubKeys, gasPrice) } - dists = append(dists, proxyDistMsgs...) // Calculate executor payouts. var execDistMsgs []types.Distribution - if filterErr == nil || !errors.Is(filterErr, types.ErrNoBasicConsensus) { + execBurnAmt := math.NewInt(0) + reducedPayout := false + switch { + case errors.Is(filterErr, types.ErrNoBasicConsensus): + execDistMsgs = k.CalculateCommitterPayouts(ctx, req, params.GasCostCommit, gasPrice) + case errors.Is(filterErr, types.ErrInvalidFilterInput) || errors.Is(filterErr, types.ErrNoConsensus) || tallyErr != nil: + reducedPayout = true + fallthrough + default: // filterErr == ErrConsensusInError || filterErr == nil gasReports := make([]uint64, len(reveals)) for i, reveal := range reveals { - gasReports[i] = max(0, reveal.GasUsed-proxyGasUsedPerExec) + if proxyGasUsedPerExec > reveal.GasUsed { + gasReports[i] = 0 + } else { + gasReports[i] = reveal.GasUsed - proxyGasUsedPerExec + } } var execGasUsed uint64 if req.ReplicationFactor == 1 || areGasReportsUniform(gasReports) { - execDistMsgs, execGasUsed = CalculateUniformPayouts(keys, gasReports[0], req.ExecGasLimit, req.ReplicationFactor, gasPrice) + execDistMsgs, execGasUsed, execBurnAmt = k.CalculateUniformPayouts(ctx, keys, gasReports[0], req.ExecGasLimit, req.ReplicationFactor, gasPrice, reducedPayout) } else { - execDistMsgs, execGasUsed = CalculateDivergentPayouts(keys, gasReports, req.ExecGasLimit, req.ReplicationFactor, gasPrice) + execDistMsgs, execGasUsed, execBurnAmt = k.CalculateDivergentPayouts(ctx, keys, gasReports, req.ExecGasLimit, req.ReplicationFactor, gasPrice, reducedPayout) } result.ExecGasUsed = execGasUsed - } else { - var err error - execDistMsgs, err = CalculateCommitterPayouts(req, gasPrice.Mul(math.NewIntFromUint64(params.GasCostCommit))) - if err != nil { - return filterResult, result, dists, err - } } - dists = append(dists, execDistMsgs...) - return filterResult, result, dists, nil + return filterResult, result, types.PayoutRecord{ + Burn: burnAmt.Add(execBurnAmt), + ProxyDists: proxyDistMsgs, + ExecDists: execDistMsgs, + } } // logErrAndRet logs the base error along with the request ID for diff --git a/x/tally/keeper/filter_and_tally_test.go b/x/tally/keeper/filter_and_tally_test.go index e0e45f69..da31bd35 100644 --- a/x/tally/keeper/filter_and_tally_test.go +++ b/x/tally/keeper/filter_and_tally_test.go @@ -6,10 +6,14 @@ import ( "testing" "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + dataproxytypes "github.com/sedaprotocol/seda-chain/x/data-proxy/types" "github.com/sedaprotocol/seda-chain/x/tally/keeper" + "github.com/sedaprotocol/seda-chain/x/tally/keeper/testdata" "github.com/sedaprotocol/seda-chain/x/tally/types" + wasmstoragetypes "github.com/sedaprotocol/seda-chain/x/wasm-storage/types" ) func TestFilterAndTally(t *testing.T) { @@ -138,7 +142,7 @@ func TestFilterAndTally(t *testing.T) { reveals[k] = revealBody } - filterRes, tallyRes, _, err := f.tallyKeeper.FilterAndTally(f.Context(), types.Request{ + filterRes, tallyRes, _ := f.tallyKeeper.FilterAndTally(f.Context(), types.Request{ Reveals: reveals, ReplicationFactor: tt.replicationFactor, ConsensusFilter: base64.StdEncoding.EncodeToString(filterInput), @@ -174,6 +178,10 @@ func TestExecutorPayout(t *testing.T) { err := f.tallyKeeper.SetParams(f.Context(), defaultParams) require.NoError(t, err) + tallyProgram := wasmstoragetypes.NewOracleProgram(testdata.SampleTallyWasm2(), f.Context().BlockTime(), f.Context().BlockHeight(), 1000) + err = f.wasmStorageKeeper.OracleProgram.Set(f.Context(), tallyProgram.Hash, tallyProgram) + require.NoError(t, err) + tests := []struct { name string tallyInputAsHex string @@ -182,9 +190,27 @@ func TestExecutorPayout(t *testing.T) { execGasLimit uint64 expExecGasUsed uint64 expExecutorRewards map[string]math.Int + expProxyRewards map[string]math.Int }{ { - name: "3/3 - Uniform gas reporting", + name: "Uniform gas reporting", + tallyInputAsHex: "00", + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "b": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "c": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + }, + replicationFactor: 3, + execGasLimit: 90000, + expExecGasUsed: 90000, + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(30000, 18), + "b": math.NewIntWithDecimal(30000, 18), + "c": math.NewIntWithDecimal(30000, 18), + }, + }, + { + name: "Uniform gas reporting beyond execGasLimit", tallyInputAsHex: "00", reveals: map[string]types.RevealBody{ "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, @@ -192,7 +218,41 @@ func TestExecutorPayout(t *testing.T) { "c": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, }, replicationFactor: 3, - execGasLimit: 30000, + execGasLimit: 60000, + expExecGasUsed: 60000, + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(20000, 18), + "b": math.NewIntWithDecimal(20000, 18), + "c": math.NewIntWithDecimal(20000, 18), + }, + }, + { + name: "Uniform gas reporting (consensus)", + tallyInputAsHex: "01000000000000000D242E726573756C742E74657874", // mode, json_path = $.result.text + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "b": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "c": {ExitCode: 0, Reveal: `{"result": {"text": "B"}}`, GasUsed: 30000}, + }, + replicationFactor: 3, + execGasLimit: 90000, + expExecGasUsed: 90000, + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(30000, 18), + "b": math.NewIntWithDecimal(30000, 18), + "c": math.NewIntWithDecimal(30000, 18), + }, + }, + { + name: "Uniform gas reporting (mode consensus in error)", + tallyInputAsHex: "01000000000000000D242E726573756C742E74657874", // mode, json_path = $.result.text + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 1, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "b": {ExitCode: 1, Reveal: `{"result": {"text": "A"}}`, GasUsed: 30000}, + "c": {ExitCode: 1, Reveal: `{"result": {"text": "B"}}`, GasUsed: 30000}, + }, + replicationFactor: 3, + execGasLimit: 90000, expExecGasUsed: 90000, expExecutorRewards: map[string]math.Int{ "a": math.NewIntWithDecimal(30000, 18), @@ -201,7 +261,24 @@ func TestExecutorPayout(t *testing.T) { }, }, { - name: "3/3 - Divergent gas reporting (1)", + name: "Uniform gas reporting (mode no consensus)", + tallyInputAsHex: "01000000000000000D242E726573756C742E74657874", // mode, json_path = $.result.text + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 20000}, + "b": {ExitCode: 0, Reveal: `{"result": {"text": "B"}}`, GasUsed: 20000}, + "c": {ExitCode: 1, Reveal: `{"result": {"text": "B"}}`, GasUsed: 20000}, + }, + replicationFactor: 3, + execGasLimit: 60000, + expExecGasUsed: 60000, + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(16000, 18), + "b": math.NewIntWithDecimal(16000, 18), + "c": math.NewIntWithDecimal(16000, 18), + }, + }, + { + name: "Divergent gas reporting (1)", tallyInputAsHex: "00", reveals: map[string]types.RevealBody{ "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 28000}, @@ -218,7 +295,7 @@ func TestExecutorPayout(t *testing.T) { }, }, { - name: "3/3 - Divergent gas reporting (2)", + name: "Divergent gas reporting (2)", tallyInputAsHex: "00", reveals: map[string]types.RevealBody{ "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 8000}, @@ -234,37 +311,102 @@ func TestExecutorPayout(t *testing.T) { "c": math.NewIntWithDecimal(20000, 18), }, }, + { + name: "Divergent gas reporting (mode no consensus)", + tallyInputAsHex: "01000000000000000D242E726573756C742E74657874", // mode, json_path = $.result.text + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 8000}, + "b": {ExitCode: 0, Reveal: `{"result": {"text": "B"}}`, GasUsed: 20000}, + "c": {ExitCode: 1, Reveal: `{"result": {"text": "B"}}`, GasUsed: 35000}, + }, + replicationFactor: 3, + execGasLimit: 90000, + expExecGasUsed: 56000, + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(16000*0.8, 18), + "b": math.NewIntWithDecimal(20000*0.8, 18), + "c": math.NewIntWithDecimal(20000*0.8, 18), + }, + }, + { + name: "Divergent gas reporting (mode no consensus)", + tallyInputAsHex: "01000000000000000D242E726573756C742E74657874", // mode, json_path = $.result.text + reveals: map[string]types.RevealBody{ + "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 8000, ProxyPubKeys: []string{"161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8"}}, + "b": {ExitCode: 0, Reveal: `{"result": {"text": "B"}}`, GasUsed: 20000, ProxyPubKeys: []string{"161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8"}}, + "c": {ExitCode: 1, Reveal: `{"result": {"text": "B"}}`, GasUsed: 35000, ProxyPubKeys: []string{"161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8"}}, + }, + replicationFactor: 3, + execGasLimit: 90000, + expExecGasUsed: 52000, // (7000, 19000, 34000) after subtracting proxy gas + expExecutorRewards: map[string]math.Int{ + "a": math.NewIntWithDecimal(14000*0.8, 18), + "b": math.NewIntWithDecimal(19000*0.8, 18), + "c": math.NewIntWithDecimal(19000*0.8, 18), + }, + expProxyRewards: map[string]math.Int{ + "161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8": math.NewIntWithDecimal(1000, 18), // = proxyFee / gasPrice + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filterInput, err := hex.DecodeString(tt.tallyInputAsHex) require.NoError(t, err) + exp21, ok := math.NewIntFromString("1000000000000000000000") // 1e21 + require.True(t, ok) + proxyFee := sdk.NewCoin(bondDenom, exp21) reveals := make(map[string]types.RevealBody) for k, v := range tt.reveals { revealBody := v revealBody.Reveal = base64.StdEncoding.EncodeToString([]byte(v.Reveal)) revealBody.GasUsed = v.GasUsed reveals[k] = revealBody + + for _, pk := range v.ProxyPubKeys { + pkBytes, err := hex.DecodeString(pk) + if err == nil { + err := f.dataProxyKeeper.SetDataProxyConfig(f.Context(), pkBytes, + dataproxytypes.ProxyConfig{ + Fee: &proxyFee, + }, + ) + require.NoError(t, err) + } + } } gasPriceStr := "1000000000000000000" // 1e18 gasPrice, ok := math.NewIntFromString(gasPriceStr) require.True(t, ok) - _, tallyRes, distMsgs, err := f.tallyKeeper.FilterAndTally(f.Context(), types.Request{ - Reveals: reveals, - ReplicationFactor: tt.replicationFactor, - ConsensusFilter: base64.StdEncoding.EncodeToString(filterInput), - GasPrice: gasPriceStr, - ExecGasLimit: tt.execGasLimit, - }, types.DefaultParams(), gasPrice) + _, tallyRes, payoutRecord := f.tallyKeeper.FilterAndTally( + f.Context(), + types.Request{ + Reveals: reveals, + ReplicationFactor: tt.replicationFactor, + ConsensusFilter: base64.StdEncoding.EncodeToString(filterInput), + GasPrice: gasPriceStr, + ExecGasLimit: tt.execGasLimit, + TallyProgramID: hex.EncodeToString(tallyProgram.Hash), + // TODO tally gas limit + }, types.DefaultParams(), gasPrice) require.NoError(t, err) - require.Equal(t, tt.expExecGasUsed, tallyRes.ExecGasUsed) - for _, distMsg := range distMsgs { - require.Equal(t, tt.expExecutorRewards[distMsg.ExecutorReward.Identity], distMsg.ExecutorReward.Amount) + for _, distMsg := range payoutRecord.ExecDists { + require.Equal(t, + tt.expExecutorRewards[distMsg.ExecutorReward.Identity].String(), + distMsg.ExecutorReward.Amount.String(), + ) } + for _, distMsg := range payoutRecord.ProxyDists { + require.Equal(t, + tt.expProxyRewards[hex.EncodeToString(distMsg.DataProxyReward.To)].String(), + distMsg.DataProxyReward.Amount.String(), + ) + } + require.Equal(t, tt.expExecGasUsed, tallyRes.ExecGasUsed) }) } } diff --git a/x/tally/keeper/filter_test.go b/x/tally/keeper/filter_test.go index d0bb78ba..ca4c9df7 100644 --- a/x/tally/keeper/filter_test.go +++ b/x/tally/keeper/filter_test.go @@ -123,7 +123,7 @@ func TestFilter(t *testing.T) { }, consensus: false, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierMode * 6, + gasUsed: 0, wantErr: types.ErrNoBasicConsensus, }, { @@ -356,7 +356,7 @@ func TestFilter(t *testing.T) { }, consensus: false, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierMode * 6, + gasUsed: 0, wantErr: types.ErrNoBasicConsensus, }, { @@ -431,7 +431,7 @@ func TestFilter(t *testing.T) { }, consensus: false, consPubKeys: nil, - gasUsed: defaultParams.FilterGasCostMultiplierMode * 4, + gasUsed: 0, wantErr: types.ErrNoBasicConsensus, }, { diff --git a/x/tally/keeper/integration_test.go b/x/tally/keeper/integration_test.go index 74db1852..c12d36b3 100644 --- a/x/tally/keeper/integration_test.go +++ b/x/tally/keeper/integration_test.go @@ -92,6 +92,7 @@ type fixture struct { wasmStorageKeeper wasmstoragekeeper.Keeper tallyKeeper keeper.Keeper batchingKeeper batchingkeeper.Keeper + dataProxyKeeper *dataproxykeeper.Keeper wasmViewKeeper wasmtypes.ViewKeeper logBuf *bytes.Buffer } @@ -106,6 +107,7 @@ func initFixture(t testing.TB) *fixture { keys := storetypes.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, sdkstakingtypes.StoreKey, wasmstoragetypes.StoreKey, wasmtypes.StoreKey, pubkeytypes.StoreKey, batchingtypes.StoreKey, types.StoreKey, + dataproxytypes.StoreKey, ) mb := module.NewBasicManager(auth.AppModuleBasic{}, bank.AppModuleBasic{}, wasmstorage.AppModuleBasic{}) @@ -319,6 +321,7 @@ func initFixture(t testing.TB) *fixture { wasmStorageKeeper: *wasmStorageKeeper, tallyKeeper: tallyKeeper, batchingKeeper: batchingKeeper, + dataProxyKeeper: dataProxyKeeper, wasmViewKeeper: wasmKeeper, logBuf: buf, } diff --git a/x/tally/keeper/payout.go b/x/tally/keeper/payout.go index 346741fb..a5240d4c 100644 --- a/x/tally/keeper/payout.go +++ b/x/tally/keeper/payout.go @@ -13,35 +13,41 @@ import ( // CalculateDataProxyPayouts returns payouts for the data proxies and // returns the the gas used by the data proxies per executor. -func (k Keeper) CalculateDataProxyPayouts(ctx sdk.Context, proxyPubKeys []string, gasPrice math.Int) ([]types.Distribution, uint64, error) { +func (k Keeper) CalculateDataProxyPayouts(ctx sdk.Context, proxyPubKeys []string, gasPrice math.Int) ([]types.Distribution, uint64) { if len(proxyPubKeys) == 0 { - return nil, 0, nil + return nil, 0 } - - gasUsed := math.NewInt(0) + gasUsed := math.ZeroInt() dists := make([]types.Distribution, len(proxyPubKeys)) for i, pubKey := range proxyPubKeys { pubKeyBytes, err := hex.DecodeString(pubKey) if err != nil { - return nil, 0, err + k.Logger(ctx).Error("failed to decode proxy public key", "error", err, "public_key", pubKey) + continue } proxyConfig, err := k.dataProxyKeeper.GetDataProxyConfig(ctx, pubKeyBytes) if err != nil { - return nil, 0, err + k.Logger(ctx).Error("failed to get proxy config", "error", err, "public_key", pubKey) + continue } - gasUsed = gasUsed.Add(proxyConfig.Fee.Amount.Quo(gasPrice)) + gasUsed = gasUsed.Add(proxyConfig.Fee.Amount.Quo(gasPrice)) dists[i] = types.NewDataProxyReward(pubKeyBytes, proxyConfig.Fee.Amount) + + ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeDataProxyReward, + sdk.NewAttribute(types.AttributeProxyPubKey, pubKey), + sdk.NewAttribute(sdk.AttributeKeyAmount, proxyConfig.Fee.Amount.String()), + )) } - return dists, gasUsed.Uint64(), nil + return dists, gasUsed.Uint64() } // CalculateCommitterPayouts returns distribution messages of a given payout // amount to the committers of a data request. The messages are sorted by // committer public key. -func CalculateCommitterPayouts(req types.Request, payout math.Int) ([]types.Distribution, error) { +func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, gasCostCommit uint64, gasPrice math.Int) []types.Distribution { if len(req.Commits) == 0 { - return nil, nil + return nil } i := 0 @@ -52,31 +58,52 @@ func CalculateCommitterPayouts(req types.Request, payout math.Int) ([]types.Dist } sort.Strings(committers) + payout := gasPrice.Mul(math.NewIntFromUint64(gasCostCommit)) dists := make([]types.Distribution, len(committers)) for i, committer := range committers { dists[i] = types.NewExecutorReward(committer, payout) + + ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeExecutorRewardCommit, + sdk.NewAttribute(types.AttributeExecutor, committer), + sdk.NewAttribute(sdk.AttributeKeyAmount, payout.String()), + )) } - return dists, nil + return dists } // CalculateUniformPayouts calculates payouts for the executors when their gas // reports are uniformly at "gasReport". It also returns the total execution gas -// consumption. -func CalculateUniformPayouts(executors []string, gasReport, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.Distribution, uint64) { - adjGasUsed := max(gasReport, execGasLimit/uint64(replicationFactor)) - payout := gasPrice.Mul(math.NewIntFromUint64(adjGasUsed)) +// consumption and, in case of reduced payout, the amount to be burned. +func (k Keeper) CalculateUniformPayouts(ctx sdk.Context, executors []string, gasReport, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int, reducedPayout bool) ([]types.Distribution, uint64, math.Int) { + adjGasUsed := min(gasReport, execGasLimit/uint64(replicationFactor)) + payoutAmt := gasPrice.Mul(math.NewIntFromUint64(adjGasUsed)) + burnAmt := math.ZeroInt() + if reducedPayout { + burnRatio := math.LegacyNewDecWithPrec(2, 1) // burn 20% + burnAmt := burnRatio.MulInt(payoutAmt).TruncateInt() + payoutAmt = payoutAmt.Sub(burnAmt) + } + totalBurn := math.ZeroInt() dists := make([]types.Distribution, len(executors)) for i, executor := range executors { - dists[i] = types.NewExecutorReward(executor, payout) + if burnAmt.IsPositive() { + totalBurn = totalBurn.Add(burnAmt) + } + dists[i] = types.NewExecutorReward(executor, payoutAmt) + + ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeExecutorRewardUniform, + sdk.NewAttribute(types.AttributeExecutor, executor), + sdk.NewAttribute(sdk.AttributeKeyAmount, payoutAmt.String()), + )) } - return dists, adjGasUsed * uint64(replicationFactor) + return dists, adjGasUsed * uint64(replicationFactor), totalBurn } // CalculateDivergentPayouts calculates payouts for the executors given their // divergent gas reports. It also returns the total execution gas consumption. // It assumes that the i-th executor is the one who revealed the i-th reveal. -func CalculateDivergentPayouts(executors []string, gasReports []uint64, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int) ([]types.Distribution, uint64) { +func (k Keeper) CalculateDivergentPayouts(ctx sdk.Context, executors []string, gasReports []uint64, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int, reducedPayout bool) ([]types.Distribution, uint64, math.Int) { adjGasUsed := make([]uint64, len(gasReports)) var lowestGasUsed uint64 var lowestReporterIndex int @@ -93,15 +120,28 @@ func CalculateDivergentPayouts(executors []string, gasReports []uint64, execGasL lowestPayout := gasPrice.Mul(math.NewIntFromUint64(lowestGasUsed * 2 * totalGasUsed / totalShares)) normalPayout := gasPrice.Mul(math.NewIntFromUint64(medianGasUsed * totalGasUsed / totalShares)) + burnRatio := math.LegacyNewDecWithPrec(2, 1) // burn 20% in case of reduced payout + + totalBurn := math.ZeroInt() dists := make([]types.Distribution, len(executors)) for i, executor := range executors { payout := normalPayout if i == lowestReporterIndex { payout = lowestPayout } + if reducedPayout { + burnAmt := burnRatio.MulInt(payout).TruncateInt() + totalBurn = totalBurn.Add(burnAmt) + payout = payout.Sub(burnAmt) + } dists[i] = types.NewExecutorReward(executor, payout) + + ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeExecutorRewardUniform, + sdk.NewAttribute(types.AttributeExecutor, executor), + sdk.NewAttribute(sdk.AttributeKeyAmount, payout.String()), + )) } - return dists, totalGasUsed + return dists, totalGasUsed, totalBurn } func median(arr []uint64) uint64 { diff --git a/x/tally/types/abci_types.go b/x/tally/types/abci_types.go index f83f184d..ee94fcf2 100644 --- a/x/tally/types/abci_types.go +++ b/x/tally/types/abci_types.go @@ -105,6 +105,26 @@ func (u *RevealBody) TryHash() (string, error) { return hex.EncodeToString(hasher.Sum(nil)), nil } +type PayoutRecord struct { + Burn math.Int `json:"burn"` + ProxyDists []Distribution `json:"proxy_dists"` + ExecDists []Distribution `json:"exec_dists"` +} + +// DistributionsWithBaseFee constructs and returns a slice of distributions that +// the contract expects based on the payout record struct and a new base fee burn. +func (p PayoutRecord) DistributionsWithBaseFee(baseFee math.Int) []Distribution { + if p.Burn.IsNil() { + p.Burn = math.ZeroInt() + } + p.Burn = p.Burn.Add(baseFee) + dists := []Distribution{} + dists = append(dists, NewBurn(p.Burn)) + dists = append(dists, p.ProxyDists...) + dists = append(dists, p.ExecDists...) + return dists +} + type Distribution struct { Burn *DistributionBurn `json:"burn,omitempty"` ExecutorReward *DistributionExecutorReward `json:"executor_reward,omitempty"` diff --git a/x/tally/types/events.go b/x/tally/types/events.go index f1ec0428..324a0881 100644 --- a/x/tally/types/events.go +++ b/x/tally/types/events.go @@ -1,7 +1,13 @@ package types const ( - EventTypeTallyCompletion = "tally_completion" + EventTypeTallyCompletion = "tally_completion" + EventTypeDataProxyReward = "data_proxy_reward" + EventTypeExecutorRewardUniform = "executor_reward_uniform" + EventTypeExecutorRewardDivergent = "executor_reward_divergent" + EventTypeExecutorRewardCommit = "executor_reward_commit" + EventTypeTallyGasBurn = "tally_gas_burn" + EventTypeBaseFeeBurn = "base_fee_burn" AttributeDataRequestID = "dr_id" AttributeDataResultID = "id" @@ -12,4 +18,6 @@ const ( AttributeTallyGasUsed = "tally_gas_used" AttributeTallyExitCode = "exit_code" AttributeProxyPubKeys = "proxy_public_keys" + AttributeProxyPubKey = "proxy_public_key" + AttributeExecutor = "executor" ) From 21f445527f381d4ed4e711234bf22e9d3969d8d9 Mon Sep 17 00:00:00 2001 From: hacheigriega Date: Fri, 17 Jan 2025 17:50:59 -0500 Subject: [PATCH 9/9] chore(x/tally): separate gas calculation, single gas calc event, make burn ratio gov param --- proto/sedachain/tally/v1/tally.proto | 12 ++ x/tally/keeper/endblock.go | 88 +++++----- x/tally/keeper/filter_and_tally_test.go | 129 +++++++------- x/tally/keeper/gas_calculation.go | 222 ++++++++++++++++++++++++ x/tally/keeper/payout.go | 167 ------------------ x/tally/types/abci_types.go | 32 +--- x/tally/types/events.go | 15 +- x/tally/types/params.go | 24 +++ x/tally/types/tally.pb.go | 102 ++++++++--- 9 files changed, 459 insertions(+), 332 deletions(-) create mode 100644 x/tally/keeper/gas_calculation.go delete mode 100644 x/tally/keeper/payout.go diff --git a/proto/sedachain/tally/v1/tally.proto b/proto/sedachain/tally/v1/tally.proto index a52345d9..b5bc3c6b 100644 --- a/proto/sedachain/tally/v1/tally.proto +++ b/proto/sedachain/tally/v1/tally.proto @@ -1,6 +1,10 @@ syntax = "proto3"; package sedachain.tally.v1; +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "amino/amino.proto"; + option go_package = "github.com/sedaprotocol/seda-chain/x/tally/types"; // Params defines the parameters for the tally module. @@ -19,4 +23,12 @@ message Params { uint64 gas_cost_base = 5; // GasCostCommit is the gas cost for a commit charged under certain scenarios. uint64 gas_cost_commit = 6; + // BurnRatio is the ratio of the gas cost to be burned in case of reduced + // payout scenarios. + string burn_ratio = 7 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (amino.dont_omitempty) = true, + (gogoproto.nullable) = false + ]; } diff --git a/x/tally/keeper/endblock.go b/x/tally/keeper/endblock.go index 24f4c0b1..88a4300c 100644 --- a/x/tally/keeper/endblock.go +++ b/x/tally/keeper/endblock.go @@ -103,20 +103,26 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err return fmt.Errorf("invalid gas price: %s", req.GasPrice) } - var record types.PayoutRecord + var gasCalc GasCalculation switch { case len(req.Commits) < int(req.ReplicationFactor): dataResults[i].Result = []byte(fmt.Sprintf("need %d commits; received %d", req.ReplicationFactor, len(req.Commits))) dataResults[i].ExitCode = TallyExitCodeNotEnoughCommits k.Logger(ctx).Info("data request's number of commits did not meet replication factor", "request_id", req.ID) - record.ExecDists = k.CalculateCommitterPayouts(ctx, req, params.GasCostCommit, gasPrice) + + gasCalc.Executors, dataResults[i].GasUsed = CommitGasUsed(req, params.GasCostCommit, req.ExecGasLimit) + dataResults[i].GasUsed += params.GasCostBase + gasCalc.Burn += params.GasCostBase case len(req.Reveals) == 0: dataResults[i].Result = []byte("no reveals") dataResults[i].ExitCode = TallyExitCodeNoReveals k.Logger(ctx).Info("data request has no reveals", "request_id", req.ID) - record.ExecDists = k.CalculateCommitterPayouts(ctx, req, params.GasCostCommit, gasPrice) + + gasCalc.Executors, dataResults[i].GasUsed = CommitGasUsed(req, params.GasCostCommit, req.ExecGasLimit) + dataResults[i].GasUsed += params.GasCostBase + gasCalc.Burn += params.GasCostBase default: - _, tallyResults[i], record = k.FilterAndTally(ctx, req, params, gasPrice) + _, tallyResults[i], gasCalc = k.FilterAndTally(ctx, req, params, gasPrice) dataResults[i].Result = tallyResults[i].Result //nolint:gosec // G115: We shouldn't get negative exit code anyway. dataResults[i].ExitCode = uint32(tallyResults[i].ExitInfo.ExitCode) @@ -127,13 +133,10 @@ func (k Keeper) ProcessTallies(ctx sdk.Context, coreContract sdk.AccAddress) err k.Logger(ctx).Debug("tally result", "request_id", req.ID, "tally_result", tallyResults[i]) } - baseFee := gasPrice.Mul(math.NewIntFromUint64(params.GasCostBase)) - processedReqs[req.ID] = record.DistributionsWithBaseFee(baseFee) - - ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeBaseFeeBurn, - sdk.NewAttribute(sdk.AttributeKeyAmount, baseFee.String()), - )) + processedReqs[req.ID] = k.DistributionsFromGasCalculation(ctx, req.ID, gasCalc, gasPrice, params.BurnRatio) + // Total gas used should not exceed the gas limit specified by the request. + dataResults[i].GasUsed = min(req.TallyGasLimit+req.ExecGasLimit, dataResults[i].GasUsed) dataResults[i].Id, err = dataResults[i].TryHash() if err != nil { return err @@ -190,7 +193,7 @@ type TallyResult struct { // FilterAndTally builds and applies filter, executes tally program, // and calculates payouts. -func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types.Params, gasPrice math.Int) (FilterResult, TallyResult, types.PayoutRecord) { +func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types.Params, gasPrice math.Int) (FilterResult, TallyResult, GasCalculation) { // Sort the reveals by their keys (executors). keys := make([]string, len(req.Reveals)) i := 0 @@ -208,19 +211,16 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. // Phase 1: Filtering filterResult, filterErr := ExecuteFilter(reveals, req.ConsensusFilter, req.ReplicationFactor, params) - // Begin tracking tally result and burn amount. + filterResult.GasUsed += params.GasCostBase + + // Begin tracking tally and gas calculation results. result := TallyResult{ Consensus: filterResult.Consensus, ProxyPubKeys: filterResult.ProxyPubKeys, TallyGasUsed: filterResult.GasUsed, } - burnAmt := math.ZeroInt() - if filterResult.GasUsed > 0 { - burnAmt = gasPrice.Mul(math.NewIntFromUint64(filterResult.GasUsed)) - ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeTallyGasBurn, - sdk.NewAttribute(sdk.AttributeKeyAmount, burnAmt.String()), - )) - } + var gasCalc GasCalculation + gasCalc.Burn += filterResult.GasUsed // Phase 2: Tally Program Execution var vmRes tallyvm.VmResult @@ -236,15 +236,8 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. result.StdOut = vmRes.Stdout result.StdErr = vmRes.Stderr } - if vmRes.GasUsed > 0 { - result.TallyGasUsed += vmRes.GasUsed - - gasFee := gasPrice.Mul(math.NewIntFromUint64(vmRes.GasUsed)) - burnAmt = burnAmt.Add(gasFee) - ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeTallyGasBurn, - sdk.NewAttribute(sdk.AttributeKeyAmount, gasFee.String()), - )) - } + result.TallyGasUsed += vmRes.GasUsed + gasCalc.Burn += vmRes.GasUsed } else { result.Result = []byte(filterErr.Error()) if errors.Is(filterErr, types.ErrInvalidFilterInput) { @@ -254,23 +247,28 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. } } - // Phase 3: Calculate Payouts - // Calculate data proxy payouts if basic consensus was reached. - var proxyDistMsgs []types.Distribution + // Phase 3: Calculate data proxy and executor gas consumption. + remainingGasLimit := req.ExecGasLimit // track execution gas limit + + // Calculate data proxy gas consumption if basic consensus was reached. var proxyGasUsedPerExec uint64 if filterErr == nil || !errors.Is(filterErr, types.ErrNoBasicConsensus) { - proxyDistMsgs, proxyGasUsedPerExec = k.CalculateDataProxyPayouts(ctx, result.ProxyPubKeys, gasPrice) + var proxyGasUsed uint64 + gasCalc.Proxies, proxyGasUsed = k.ProxyGasUsed(ctx, result.ProxyPubKeys, gasPrice, req.ExecGasLimit, req.ReplicationFactor) + + remainingGasLimit -= proxyGasUsed + proxyGasUsedPerExec = proxyGasUsed / uint64(req.ReplicationFactor) + result.ExecGasUsed += proxyGasUsed } - // Calculate executor payouts. - var execDistMsgs []types.Distribution - execBurnAmt := math.NewInt(0) - reducedPayout := false + // Calculate executor gas consumption. switch { case errors.Is(filterErr, types.ErrNoBasicConsensus): - execDistMsgs = k.CalculateCommitterPayouts(ctx, req, params.GasCostCommit, gasPrice) + var commitGasUsed uint64 + gasCalc.Executors, commitGasUsed = CommitGasUsed(req, params.GasCostCommit, remainingGasLimit) + result.ExecGasUsed += commitGasUsed case errors.Is(filterErr, types.ErrInvalidFilterInput) || errors.Is(filterErr, types.ErrNoConsensus) || tallyErr != nil: - reducedPayout = true + gasCalc.ReducedPayout = true fallthrough default: // filterErr == ErrConsensusInError || filterErr == nil gasReports := make([]uint64, len(reveals)) @@ -283,19 +281,15 @@ func (k Keeper) FilterAndTally(ctx sdk.Context, req types.Request, params types. } var execGasUsed uint64 - if req.ReplicationFactor == 1 || areGasReportsUniform(gasReports) { - execDistMsgs, execGasUsed, execBurnAmt = k.CalculateUniformPayouts(ctx, keys, gasReports[0], req.ExecGasLimit, req.ReplicationFactor, gasPrice, reducedPayout) + if areGasReportsUniform(gasReports) { + gasCalc.Executors, execGasUsed = ExecutorGasUsedUniform(keys, gasReports[0], remainingGasLimit, req.ReplicationFactor) } else { - execDistMsgs, execGasUsed, execBurnAmt = k.CalculateDivergentPayouts(ctx, keys, gasReports, req.ExecGasLimit, req.ReplicationFactor, gasPrice, reducedPayout) + gasCalc.Executors, execGasUsed = ExecutorGasUsedDivergent(keys, gasReports, remainingGasLimit, req.ReplicationFactor) } - result.ExecGasUsed = execGasUsed + result.ExecGasUsed += execGasUsed } - return filterResult, result, types.PayoutRecord{ - Burn: burnAmt.Add(execBurnAmt), - ProxyDists: proxyDistMsgs, - ExecDists: execDistMsgs, - } + return filterResult, result, gasCalc } // logErrAndRet logs the base error along with the request ID for diff --git a/x/tally/keeper/filter_and_tally_test.go b/x/tally/keeper/filter_and_tally_test.go index da31bd35..cf779a78 100644 --- a/x/tally/keeper/filter_and_tally_test.go +++ b/x/tally/keeper/filter_and_tally_test.go @@ -48,7 +48,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: true, consPubKeys: nil, - filterGasUsed: defaultParams.FilterGasCostNone, + filterGasUsed: defaultParams.GasCostBase + defaultParams.FilterGasCostNone, exitCode: keeper.TallyExitCodeExecError, // since tally program does not exist filterErr: nil, }, @@ -62,7 +62,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - filterGasUsed: 0, + filterGasUsed: defaultParams.GasCostBase, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -79,7 +79,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: true, consPubKeys: nil, - filterGasUsed: defaultParams.FilterGasCostMultiplierMode * 5, + filterGasUsed: defaultParams.GasCostBase + defaultParams.FilterGasCostMultiplierMode*5, exitCode: keeper.TallyExitCodeExecError, // since tally program does not exist filterErr: nil, }, @@ -93,7 +93,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - filterGasUsed: 0, + filterGasUsed: defaultParams.GasCostBase, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -110,7 +110,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: true, consPubKeys: nil, - filterGasUsed: defaultParams.FilterGasCostMultiplierStdDev * 5, + filterGasUsed: defaultParams.GasCostBase + defaultParams.FilterGasCostMultiplierStdDev*5, exitCode: keeper.TallyExitCodeExecError, // since tally program does not exist filterErr: nil, }, @@ -124,7 +124,7 @@ func TestFilterAndTally(t *testing.T) { replicationFactor: 5, consensus: false, consPubKeys: nil, - filterGasUsed: 0, + filterGasUsed: defaultParams.GasCostBase, exitCode: keeper.TallyExitCodeFilterError, filterErr: types.ErrNoBasicConsensus, }, @@ -183,14 +183,15 @@ func TestExecutorPayout(t *testing.T) { require.NoError(t, err) tests := []struct { - name string - tallyInputAsHex string - reveals map[string]types.RevealBody - replicationFactor uint16 - execGasLimit uint64 - expExecGasUsed uint64 - expExecutorRewards map[string]math.Int - expProxyRewards map[string]math.Int + name string + tallyInputAsHex string + reveals map[string]types.RevealBody + replicationFactor uint16 + execGasLimit uint64 + expExecGasUsed uint64 + expReducedPayout bool + expExecutorGas map[string]math.Int + expProxyGas map[string]math.Int }{ { name: "Uniform gas reporting", @@ -203,10 +204,10 @@ func TestExecutorPayout(t *testing.T) { replicationFactor: 3, execGasLimit: 90000, expExecGasUsed: 90000, - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(30000, 18), - "b": math.NewIntWithDecimal(30000, 18), - "c": math.NewIntWithDecimal(30000, 18), + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(30000), + "b": math.NewInt(30000), + "c": math.NewInt(30000), }, }, { @@ -220,10 +221,10 @@ func TestExecutorPayout(t *testing.T) { replicationFactor: 3, execGasLimit: 60000, expExecGasUsed: 60000, - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(20000, 18), - "b": math.NewIntWithDecimal(20000, 18), - "c": math.NewIntWithDecimal(20000, 18), + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(20000), + "b": math.NewInt(20000), + "c": math.NewInt(20000), }, }, { @@ -237,10 +238,10 @@ func TestExecutorPayout(t *testing.T) { replicationFactor: 3, execGasLimit: 90000, expExecGasUsed: 90000, - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(30000, 18), - "b": math.NewIntWithDecimal(30000, 18), - "c": math.NewIntWithDecimal(30000, 18), + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(30000), + "b": math.NewInt(30000), + "c": math.NewInt(30000), }, }, { @@ -254,10 +255,10 @@ func TestExecutorPayout(t *testing.T) { replicationFactor: 3, execGasLimit: 90000, expExecGasUsed: 90000, - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(30000, 18), - "b": math.NewIntWithDecimal(30000, 18), - "c": math.NewIntWithDecimal(30000, 18), + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(30000), + "b": math.NewInt(30000), + "c": math.NewInt(30000), }, }, { @@ -271,11 +272,12 @@ func TestExecutorPayout(t *testing.T) { replicationFactor: 3, execGasLimit: 60000, expExecGasUsed: 60000, - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(16000, 18), - "b": math.NewIntWithDecimal(16000, 18), - "c": math.NewIntWithDecimal(16000, 18), + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(20000), + "b": math.NewInt(20000), + "c": math.NewInt(20000), }, + expReducedPayout: true, }, { name: "Divergent gas reporting (1)", @@ -288,10 +290,10 @@ func TestExecutorPayout(t *testing.T) { replicationFactor: 3, execGasLimit: 90000, expExecGasUsed: 90000, - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(43448, 18), - "b": math.NewIntWithDecimal(23275, 18), - "c": math.NewIntWithDecimal(23275, 18), + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(43448), + "b": math.NewInt(23275), + "c": math.NewInt(23275), }, }, { @@ -305,10 +307,10 @@ func TestExecutorPayout(t *testing.T) { replicationFactor: 3, execGasLimit: 90000, expExecGasUsed: 56000, - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(16000, 18), - "b": math.NewIntWithDecimal(20000, 18), - "c": math.NewIntWithDecimal(20000, 18), + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(16000), + "b": math.NewInt(20000), + "c": math.NewInt(20000), }, }, { @@ -322,30 +324,32 @@ func TestExecutorPayout(t *testing.T) { replicationFactor: 3, execGasLimit: 90000, expExecGasUsed: 56000, - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(16000*0.8, 18), - "b": math.NewIntWithDecimal(20000*0.8, 18), - "c": math.NewIntWithDecimal(20000*0.8, 18), + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(16000), + "b": math.NewInt(20000), + "c": math.NewInt(20000), }, + expReducedPayout: true, }, { - name: "Divergent gas reporting (mode no consensus)", + name: "Divergent gas reporting (mode no consensus, with proxies)", tallyInputAsHex: "01000000000000000D242E726573756C742E74657874", // mode, json_path = $.result.text - reveals: map[string]types.RevealBody{ + reveals: map[string]types.RevealBody{ // (7000, 19000, 34000) after subtracting proxy gas "a": {ExitCode: 0, Reveal: `{"result": {"text": "A"}}`, GasUsed: 8000, ProxyPubKeys: []string{"161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8"}}, "b": {ExitCode: 0, Reveal: `{"result": {"text": "B"}}`, GasUsed: 20000, ProxyPubKeys: []string{"161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8"}}, "c": {ExitCode: 1, Reveal: `{"result": {"text": "B"}}`, GasUsed: 35000, ProxyPubKeys: []string{"161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8"}}, }, replicationFactor: 3, execGasLimit: 90000, - expExecGasUsed: 52000, // (7000, 19000, 34000) after subtracting proxy gas - expExecutorRewards: map[string]math.Int{ - "a": math.NewIntWithDecimal(14000*0.8, 18), - "b": math.NewIntWithDecimal(19000*0.8, 18), - "c": math.NewIntWithDecimal(19000*0.8, 18), + expExecGasUsed: 55000, + expExecutorGas: map[string]math.Int{ + "a": math.NewInt(14000), + "b": math.NewInt(19000), + "c": math.NewInt(19000), }, - expProxyRewards: map[string]math.Int{ - "161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8": math.NewIntWithDecimal(1000, 18), // = proxyFee / gasPrice + expReducedPayout: true, + expProxyGas: map[string]math.Int{ + "161b0d3a1efbf2f7d2f130f68a2ccf8f8f3220e8": math.NewInt(3000), // = RF * proxyFee / gasPrice }, }, } @@ -381,7 +385,7 @@ func TestExecutorPayout(t *testing.T) { gasPrice, ok := math.NewIntFromString(gasPriceStr) require.True(t, ok) - _, tallyRes, payoutRecord := f.tallyKeeper.FilterAndTally( + _, tallyRes, gasCalc := f.tallyKeeper.FilterAndTally( f.Context(), types.Request{ Reveals: reveals, @@ -394,16 +398,17 @@ func TestExecutorPayout(t *testing.T) { }, types.DefaultParams(), gasPrice) require.NoError(t, err) - for _, distMsg := range payoutRecord.ExecDists { + for _, exec := range gasCalc.Executors { require.Equal(t, - tt.expExecutorRewards[distMsg.ExecutorReward.Identity].String(), - distMsg.ExecutorReward.Amount.String(), + tt.expExecutorGas[exec.PublicKey].String(), + exec.Amount.String(), ) } - for _, distMsg := range payoutRecord.ProxyDists { + require.Equal(t, tt.expReducedPayout, gasCalc.ReducedPayout) + for _, proxy := range gasCalc.Proxies { require.Equal(t, - tt.expProxyRewards[hex.EncodeToString(distMsg.DataProxyReward.To)].String(), - distMsg.DataProxyReward.Amount.String(), + tt.expProxyGas[hex.EncodeToString(proxy.PublicKey)].String(), + proxy.Amount.String(), ) } require.Equal(t, tt.expExecGasUsed, tallyRes.ExecGasUsed) diff --git a/x/tally/keeper/gas_calculation.go b/x/tally/keeper/gas_calculation.go new file mode 100644 index 00000000..3db9a64d --- /dev/null +++ b/x/tally/keeper/gas_calculation.go @@ -0,0 +1,222 @@ +package keeper + +import ( + "encoding/hex" + "fmt" + "sort" + "strconv" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/sedaprotocol/seda-chain/x/tally/types" +) + +// GasCalculation stores the results of the canonical gas consumption calculations. +type GasCalculation struct { + Burn uint64 // filter and tally gas to be burned + Proxies []ProxyGasUsed + Executors []ExecutorGasUsed + ReducedPayout bool +} + +type ProxyGasUsed struct { + PublicKey []byte + Amount math.Int +} + +type ExecutorGasUsed struct { + PublicKey string + Amount math.Int +} + +// DistributionsFromGasCalculation constructs a list of distribution messages +// to be sent to the core contract based on the given gas calculation. +func (k Keeper) DistributionsFromGasCalculation(ctx sdk.Context, reqID string, gasCalc GasCalculation, gasPrice math.Int, burnRatio math.LegacyDec) []types.Distribution { + dists := []types.Distribution{} + attrs := []sdk.Attribute{ + sdk.NewAttribute(types.AttributeDataRequestID, reqID), + sdk.NewAttribute(types.AttributeReducedPayout, strconv.FormatBool(gasCalc.ReducedPayout)), + } + + // First distribution message is the combined burn. + burn := types.NewBurn(math.NewIntFromUint64(gasCalc.Burn), gasPrice) + dists = append(dists, burn) + attrs = append(attrs, sdk.NewAttribute(types.AttributeTallyGas, strconv.FormatUint(gasCalc.Burn, 10))) + + // Append distribution messages for data proxies. + for _, proxy := range gasCalc.Proxies { + proxyDist := types.NewDataProxyReward(proxy.PublicKey, proxy.Amount, gasPrice) + dists = append(dists, proxyDist) + attrs = append(attrs, sdk.NewAttribute(types.AttributeDataProxyGas, + fmt.Sprintf("%s,%s", hex.EncodeToString(proxy.PublicKey), proxy.Amount.String()))) + } + + // Append distribution messages for executors, burning a portion of their + // payouts in case of a reduced payout scenario. + addBurn := math.ZeroInt() + for _, executor := range gasCalc.Executors { + payoutAmt := executor.Amount + if gasCalc.ReducedPayout { + burnAmt := burnRatio.MulInt(executor.Amount).TruncateInt() + payoutAmt = executor.Amount.Sub(burnAmt) + addBurn = addBurn.Add(burnAmt.Mul(gasPrice)) + } + + executorDist := types.NewExecutorReward(executor.PublicKey, payoutAmt, gasPrice) + dists = append(dists, executorDist) + attrs = append(attrs, sdk.NewAttribute(types.AttributeExecutorGas, + fmt.Sprintf("%s,%s", executor.PublicKey, payoutAmt.String()))) + } + + // Add additional burn. + dists[0].Burn.Amount = dists[0].Burn.Amount.Add(addBurn.Mul(gasPrice)) + + ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeGasCalculation, attrs...)) + + return dists +} + +// ProxyGasUsed returns the results of data proxy gas calculations, the total +// gas used, and the remaining gas limit per executor. +func (k Keeper) ProxyGasUsed(ctx sdk.Context, proxyPubKeys []string, gasPrice math.Int, gasLimit uint64, replicationFactor uint16) ([]ProxyGasUsed, uint64) { + if len(proxyPubKeys) == 0 || gasLimit == 0 { + return nil, 0 + } + gasLimitPerExec := gasLimit / uint64(replicationFactor) + + var totalGasUsed uint64 + calcs := []ProxyGasUsed{} + for _, pubKey := range proxyPubKeys { + pubKeyBytes, err := hex.DecodeString(pubKey) + if err != nil { + k.Logger(ctx).Error("failed to decode proxy public key", "error", err, "public_key", pubKey) + continue + } + proxyConfig, err := k.dataProxyKeeper.GetDataProxyConfig(ctx, pubKeyBytes) + if err != nil { + k.Logger(ctx).Error("failed to get proxy config", "error", err, "public_key", pubKey) + continue + } + + gasUsedPerExec := proxyConfig.Fee.Amount.Quo(gasPrice).Uint64() + gasUsedPerExec = min(gasUsedPerExec, gasLimitPerExec) + gasUsed := gasUsedPerExec * uint64(replicationFactor) + + calcs = append(calcs, ProxyGasUsed{ + PublicKey: pubKeyBytes, + Amount: math.NewIntFromUint64(gasUsed), + }) + + totalGasUsed += gasUsed + gasLimitPerExec -= gasUsedPerExec + } + return calcs, totalGasUsed +} + +// CommitGasUsed processes gas consumption of committers of a given data request. +func CommitGasUsed(req types.Request, gasCostCommit, gasLimit uint64) ([]ExecutorGasUsed, uint64) { + if len(req.Commits) == 0 || gasLimit == 0 { + return nil, 0 + } + gasLimitPerExec := gasLimit / uint64(req.ReplicationFactor) + + i := 0 + committers := make([]string, len(req.Commits)) + for k := range req.Commits { + committers[i] = k + i++ + } + sort.Strings(committers) + + var totalGasUsed uint64 + calcs := make([]ExecutorGasUsed, len(committers)) + for i, committer := range committers { + gasUsed := min(gasLimitPerExec, gasCostCommit) + calcs[i] = ExecutorGasUsed{ + PublicKey: committer, + Amount: math.NewIntFromUint64(gasUsed), + } + totalGasUsed += gasUsed + } + return calcs, totalGasUsed +} + +// ProcessGasUsedUniform calculates the canonical gas consumption by the +// executors when their gas reports are uniformly at "gasReport". It also returns +// the total execution gas consumption. +func ExecutorGasUsedUniform(executors []string, gasReport, gasLimit uint64, replicationFactor uint16) ([]ExecutorGasUsed, uint64) { + gasUsed := min(gasReport, gasLimit/uint64(replicationFactor)) + + var totalGasUsed uint64 + calcs := make([]ExecutorGasUsed, len(executors)) + for i, executor := range executors { + calcs[i] = ExecutorGasUsed{ + PublicKey: executor, + Amount: math.NewIntFromUint64(gasUsed), + } + totalGasUsed += gasUsed + } + return calcs, totalGasUsed +} + +// ProcessGasUsedDivergent calculates the canonical gas consumption by the +// executors given their divergent gas reports. It also returns the total +// execution gas consumption. +func ExecutorGasUsedDivergent(executors []string, gasReports []uint64, gasLimit uint64, replicationFactor uint16) ([]ExecutorGasUsed, uint64) { + var lowestReport uint64 + var lowestReporterIndex int + adjGasReports := make([]uint64, len(gasReports)) + for i, gasReport := range gasReports { + adjGasReports[i] = min(gasReport, gasLimit/uint64(replicationFactor)) + if i == 0 || adjGasReports[i] < lowestReport { + lowestReporterIndex = i + lowestReport = adjGasReports[i] + } + } + medianGasUsed := median(adjGasReports) + totalGasUsed := medianGasUsed*uint64(replicationFactor-1) + min(lowestReport*2, medianGasUsed) + totalShares := medianGasUsed*uint64(replicationFactor-1) + lowestReport*2 + lowestGasUsed := math.NewIntFromUint64(lowestReport * 2 * totalGasUsed / totalShares) + regGasUsed := math.NewIntFromUint64(medianGasUsed * totalGasUsed / totalShares) + + calcs := make([]ExecutorGasUsed, len(executors)) + for i, executor := range executors { + gasUsed := regGasUsed + if i == lowestReporterIndex { + gasUsed = lowestGasUsed + } + calcs[i] = ExecutorGasUsed{ + PublicKey: executor, + Amount: gasUsed, + } + } + return calcs, totalGasUsed +} + +func median(arr []uint64) uint64 { + sort.Slice(arr, func(i, j int) bool { + return arr[i] < arr[j] + }) + n := len(arr) + if n%2 == 0 { + return (arr[n/2-1] + arr[n/2]) / 2 + } + return arr[n/2] +} + +// areGasReportsUniform returns true if the gas reports of the given reveals are +// uniform. +func areGasReportsUniform(reports []uint64) bool { + if len(reports) <= 1 { + return true + } + firstGas := reports[0] + for i := 1; i < len(reports); i++ { + if reports[i] != firstGas { + return false + } + } + return true +} diff --git a/x/tally/keeper/payout.go b/x/tally/keeper/payout.go deleted file mode 100644 index a5240d4c..00000000 --- a/x/tally/keeper/payout.go +++ /dev/null @@ -1,167 +0,0 @@ -package keeper - -import ( - "encoding/hex" - "sort" - - "cosmossdk.io/math" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/sedaprotocol/seda-chain/x/tally/types" -) - -// CalculateDataProxyPayouts returns payouts for the data proxies and -// returns the the gas used by the data proxies per executor. -func (k Keeper) CalculateDataProxyPayouts(ctx sdk.Context, proxyPubKeys []string, gasPrice math.Int) ([]types.Distribution, uint64) { - if len(proxyPubKeys) == 0 { - return nil, 0 - } - gasUsed := math.ZeroInt() - dists := make([]types.Distribution, len(proxyPubKeys)) - for i, pubKey := range proxyPubKeys { - pubKeyBytes, err := hex.DecodeString(pubKey) - if err != nil { - k.Logger(ctx).Error("failed to decode proxy public key", "error", err, "public_key", pubKey) - continue - } - proxyConfig, err := k.dataProxyKeeper.GetDataProxyConfig(ctx, pubKeyBytes) - if err != nil { - k.Logger(ctx).Error("failed to get proxy config", "error", err, "public_key", pubKey) - continue - } - - gasUsed = gasUsed.Add(proxyConfig.Fee.Amount.Quo(gasPrice)) - dists[i] = types.NewDataProxyReward(pubKeyBytes, proxyConfig.Fee.Amount) - - ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeDataProxyReward, - sdk.NewAttribute(types.AttributeProxyPubKey, pubKey), - sdk.NewAttribute(sdk.AttributeKeyAmount, proxyConfig.Fee.Amount.String()), - )) - } - return dists, gasUsed.Uint64() -} - -// CalculateCommitterPayouts returns distribution messages of a given payout -// amount to the committers of a data request. The messages are sorted by -// committer public key. -func (k Keeper) CalculateCommitterPayouts(ctx sdk.Context, req types.Request, gasCostCommit uint64, gasPrice math.Int) []types.Distribution { - if len(req.Commits) == 0 { - return nil - } - - i := 0 - committers := make([]string, len(req.Commits)) - for k := range req.Commits { - committers[i] = k - i++ - } - sort.Strings(committers) - - payout := gasPrice.Mul(math.NewIntFromUint64(gasCostCommit)) - dists := make([]types.Distribution, len(committers)) - for i, committer := range committers { - dists[i] = types.NewExecutorReward(committer, payout) - - ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeExecutorRewardCommit, - sdk.NewAttribute(types.AttributeExecutor, committer), - sdk.NewAttribute(sdk.AttributeKeyAmount, payout.String()), - )) - } - return dists -} - -// CalculateUniformPayouts calculates payouts for the executors when their gas -// reports are uniformly at "gasReport". It also returns the total execution gas -// consumption and, in case of reduced payout, the amount to be burned. -func (k Keeper) CalculateUniformPayouts(ctx sdk.Context, executors []string, gasReport, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int, reducedPayout bool) ([]types.Distribution, uint64, math.Int) { - adjGasUsed := min(gasReport, execGasLimit/uint64(replicationFactor)) - payoutAmt := gasPrice.Mul(math.NewIntFromUint64(adjGasUsed)) - burnAmt := math.ZeroInt() - if reducedPayout { - burnRatio := math.LegacyNewDecWithPrec(2, 1) // burn 20% - burnAmt := burnRatio.MulInt(payoutAmt).TruncateInt() - payoutAmt = payoutAmt.Sub(burnAmt) - } - - totalBurn := math.ZeroInt() - dists := make([]types.Distribution, len(executors)) - for i, executor := range executors { - if burnAmt.IsPositive() { - totalBurn = totalBurn.Add(burnAmt) - } - dists[i] = types.NewExecutorReward(executor, payoutAmt) - - ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeExecutorRewardUniform, - sdk.NewAttribute(types.AttributeExecutor, executor), - sdk.NewAttribute(sdk.AttributeKeyAmount, payoutAmt.String()), - )) - } - return dists, adjGasUsed * uint64(replicationFactor), totalBurn -} - -// CalculateDivergentPayouts calculates payouts for the executors given their -// divergent gas reports. It also returns the total execution gas consumption. -// It assumes that the i-th executor is the one who revealed the i-th reveal. -func (k Keeper) CalculateDivergentPayouts(ctx sdk.Context, executors []string, gasReports []uint64, execGasLimit uint64, replicationFactor uint16, gasPrice math.Int, reducedPayout bool) ([]types.Distribution, uint64, math.Int) { - adjGasUsed := make([]uint64, len(gasReports)) - var lowestGasUsed uint64 - var lowestReporterIndex int - for i, gasReport := range gasReports { - adjGasUsed[i] = min(gasReport, execGasLimit/uint64(replicationFactor)) - if i == 0 || adjGasUsed[i] < lowestGasUsed { - lowestReporterIndex = i - lowestGasUsed = adjGasUsed[i] - } - } - medianGasUsed := median(adjGasUsed) - totalGasUsed := medianGasUsed*uint64(replicationFactor-1) + min(lowestGasUsed*2, medianGasUsed) - totalShares := medianGasUsed*uint64(replicationFactor-1) + lowestGasUsed*2 - lowestPayout := gasPrice.Mul(math.NewIntFromUint64(lowestGasUsed * 2 * totalGasUsed / totalShares)) - normalPayout := gasPrice.Mul(math.NewIntFromUint64(medianGasUsed * totalGasUsed / totalShares)) - - burnRatio := math.LegacyNewDecWithPrec(2, 1) // burn 20% in case of reduced payout - - totalBurn := math.ZeroInt() - dists := make([]types.Distribution, len(executors)) - for i, executor := range executors { - payout := normalPayout - if i == lowestReporterIndex { - payout = lowestPayout - } - if reducedPayout { - burnAmt := burnRatio.MulInt(payout).TruncateInt() - totalBurn = totalBurn.Add(burnAmt) - payout = payout.Sub(burnAmt) - } - dists[i] = types.NewExecutorReward(executor, payout) - - ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventTypeExecutorRewardUniform, - sdk.NewAttribute(types.AttributeExecutor, executor), - sdk.NewAttribute(sdk.AttributeKeyAmount, payout.String()), - )) - } - return dists, totalGasUsed, totalBurn -} - -func median(arr []uint64) uint64 { - sort.Slice(arr, func(i, j int) bool { - return arr[i] < arr[j] - }) - return arr[len(arr)/2] -} - -// areGasReportsUniform returns true if the gas reports of the given reveals are -// uniform. -func areGasReportsUniform(reports []uint64) bool { - if len(reports) == 0 { - return true - } - firstGas := reports[0] - for i := 1; i < len(reports); i++ { - if reports[i] != firstGas { - return false - } - } - return true -} diff --git a/x/tally/types/abci_types.go b/x/tally/types/abci_types.go index ee94fcf2..206df4ed 100644 --- a/x/tally/types/abci_types.go +++ b/x/tally/types/abci_types.go @@ -105,26 +105,6 @@ func (u *RevealBody) TryHash() (string, error) { return hex.EncodeToString(hasher.Sum(nil)), nil } -type PayoutRecord struct { - Burn math.Int `json:"burn"` - ProxyDists []Distribution `json:"proxy_dists"` - ExecDists []Distribution `json:"exec_dists"` -} - -// DistributionsWithBaseFee constructs and returns a slice of distributions that -// the contract expects based on the payout record struct and a new base fee burn. -func (p PayoutRecord) DistributionsWithBaseFee(baseFee math.Int) []Distribution { - if p.Burn.IsNil() { - p.Burn = math.ZeroInt() - } - p.Burn = p.Burn.Add(baseFee) - dists := []Distribution{} - dists = append(dists, NewBurn(p.Burn)) - dists = append(dists, p.ProxyDists...) - dists = append(dists, p.ExecDists...) - return dists -} - type Distribution struct { Burn *DistributionBurn `json:"burn,omitempty"` ExecutorReward *DistributionExecutorReward `json:"executor_reward,omitempty"` @@ -145,26 +125,26 @@ type DistributionExecutorReward struct { Identity string `json:"identity"` } -func NewBurn(amount math.Int) Distribution { +func NewBurn(amount, gasPrice math.Int) Distribution { return Distribution{ - Burn: &DistributionBurn{Amount: amount}, + Burn: &DistributionBurn{Amount: amount.Mul(gasPrice)}, } } -func NewDataProxyReward(pubKey []byte, amount math.Int) Distribution { +func NewDataProxyReward(pubKey []byte, amount, gasPrice math.Int) Distribution { return Distribution{ DataProxyReward: &DistributionDataProxyReward{ To: pubKey, - Amount: amount, + Amount: amount.Mul(gasPrice), }, } } -func NewExecutorReward(identity string, amount math.Int) Distribution { +func NewExecutorReward(identity string, amount, gasPrice math.Int) Distribution { return Distribution{ ExecutorReward: &DistributionExecutorReward{ Identity: identity, - Amount: amount, + Amount: amount.Mul(gasPrice), }, } } diff --git a/x/tally/types/events.go b/x/tally/types/events.go index 324a0881..7f58e5f3 100644 --- a/x/tally/types/events.go +++ b/x/tally/types/events.go @@ -1,13 +1,8 @@ package types const ( - EventTypeTallyCompletion = "tally_completion" - EventTypeDataProxyReward = "data_proxy_reward" - EventTypeExecutorRewardUniform = "executor_reward_uniform" - EventTypeExecutorRewardDivergent = "executor_reward_divergent" - EventTypeExecutorRewardCommit = "executor_reward_commit" - EventTypeTallyGasBurn = "tally_gas_burn" - EventTypeBaseFeeBurn = "base_fee_burn" + EventTypeTallyCompletion = "tally_completion" + EventTypeGasCalculation = "gas_calculation" AttributeDataRequestID = "dr_id" AttributeDataResultID = "id" @@ -18,6 +13,8 @@ const ( AttributeTallyGasUsed = "tally_gas_used" AttributeTallyExitCode = "exit_code" AttributeProxyPubKeys = "proxy_public_keys" - AttributeProxyPubKey = "proxy_public_key" - AttributeExecutor = "executor" + AttributeTallyGas = "tally_gas" + AttributeDataProxyGas = "data_proxy_gas" + AttributeExecutorGas = "executor_reward_gas" + AttributeReducedPayout = "reduced_payout" ) diff --git a/x/tally/types/params.go b/x/tally/types/params.go index 8d54fd05..3ba7f591 100644 --- a/x/tally/types/params.go +++ b/x/tally/types/params.go @@ -1,6 +1,10 @@ package types import ( + fmt "fmt" + + "cosmossdk.io/math" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -13,6 +17,8 @@ const ( DefaultGasCostCommit = 5_000_000_000_000 ) +var DefaultBurnRatio = math.LegacyNewDecWithPrec(2, 1) + // DefaultParams returns default tally module parameters. func DefaultParams() Params { return Params{ @@ -22,6 +28,7 @@ func DefaultParams() Params { FilterGasCostMultiplierStdDev: DefaultFilterGasCostMultiplierStddev, GasCostBase: DefaultGasCostBase, GasCostCommit: DefaultGasCostCommit, + BurnRatio: DefaultBurnRatio, } } @@ -45,5 +52,22 @@ func (p *Params) Validate() error { if p.GasCostCommit <= 0 { return sdkerrors.ErrInvalidRequest.Wrapf("commit gas cost must be greater than 0: %d", p.GasCostCommit) } + return validateBurnRatio(p.BurnRatio) +} + +func validateBurnRatio(i interface{}) error { + v, ok := i.(math.LegacyDec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + if v.IsNil() { + return fmt.Errorf("burn ratio must be not nil") + } + if v.IsNegative() { + return fmt.Errorf("burn ratio must be positive: %s", v) + } + if v.GT(math.LegacyOneDec()) { + return fmt.Errorf("burn ratio too large: %s", v) + } return nil } diff --git a/x/tally/types/tally.pb.go b/x/tally/types/tally.pb.go index 98c4d52f..0359622c 100644 --- a/x/tally/types/tally.pb.go +++ b/x/tally/types/tally.pb.go @@ -4,7 +4,11 @@ package types import ( + cosmossdk_io_math "cosmossdk.io/math" fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -38,6 +42,9 @@ type Params struct { GasCostBase uint64 `protobuf:"varint,5,opt,name=gas_cost_base,json=gasCostBase,proto3" json:"gas_cost_base,omitempty"` // GasCostCommit is the gas cost for a commit charged under certain scenarios. GasCostCommit uint64 `protobuf:"varint,6,opt,name=gas_cost_commit,json=gasCostCommit,proto3" json:"gas_cost_commit,omitempty"` + // BurnRatio is the ratio of the gas cost to be burned in case of reduced payout + // scenarios. + BurnRatio cosmossdk_io_math.LegacyDec `protobuf:"bytes,7,opt,name=burn_ratio,json=burnRatio,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"burn_ratio"` } func (m *Params) Reset() { *m = Params{} } @@ -122,27 +129,34 @@ func init() { func init() { proto.RegisterFile("sedachain/tally/v1/tally.proto", fileDescriptor_2917df8a6808d5e2) } var fileDescriptor_2917df8a6808d5e2 = []byte{ - // 312 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0xc1, 0x4a, 0x03, 0x31, - 0x18, 0x84, 0xbb, 0xb5, 0xf6, 0x10, 0x29, 0x6a, 0xf4, 0xb0, 0x20, 0x46, 0xe9, 0x41, 0xbc, 0xb4, - 0x6b, 0xf1, 0x0d, 0xda, 0x42, 0x51, 0xac, 0x88, 0x7a, 0xf2, 0x12, 0xfe, 0x6e, 0x7e, 0xdb, 0x40, - 0xb2, 0x29, 0x9b, 0x74, 0x69, 0xdf, 0xc2, 0x97, 0x12, 0x3c, 0xf6, 0xe8, 0x51, 0xba, 0x2f, 0x22, - 0x9b, 0x2d, 0x0b, 0x0a, 0x7a, 0x4b, 0x66, 0xbe, 0x99, 0xc3, 0x3f, 0x84, 0x59, 0x14, 0x10, 0xcf, - 0x40, 0x26, 0x91, 0x03, 0xa5, 0x56, 0x51, 0xd6, 0x2b, 0x1f, 0xdd, 0x79, 0x6a, 0x9c, 0xa1, 0xb4, - 0xf2, 0xbb, 0xa5, 0x9c, 0xf5, 0xda, 0xef, 0x75, 0xd2, 0x7c, 0x80, 0x14, 0xb4, 0xa5, 0x1d, 0x72, - 0xa4, 0x61, 0xc9, 0xbd, 0xc5, 0xa7, 0x60, 0xb9, 0x92, 0x5a, 0xba, 0x30, 0x38, 0x0f, 0x2e, 0x1b, - 0x8f, 0x07, 0x1a, 0x96, 0xcf, 0x85, 0x33, 0x02, 0x7b, 0x57, 0xe8, 0x34, 0x22, 0xc7, 0xaf, 0x52, - 0x39, 0x4c, 0x3d, 0x1b, 0x1b, 0xeb, 0x78, 0x62, 0x12, 0x0c, 0xeb, 0x9e, 0x3f, 0x2c, 0xbd, 0x11, - 0xd8, 0x81, 0xb1, 0xee, 0xde, 0x24, 0x48, 0x87, 0xe4, 0xec, 0x77, 0x40, 0x2f, 0x94, 0x93, 0x73, - 0x25, 0x31, 0xe5, 0xda, 0x08, 0x0c, 0x77, 0x7c, 0xf6, 0xe4, 0x47, 0x76, 0x5c, 0x31, 0x63, 0x23, - 0x90, 0xde, 0x90, 0xf6, 0x3f, 0x2d, 0xd6, 0x09, 0x2e, 0x30, 0x0b, 0x1b, 0xbe, 0xe8, 0xf4, 0x8f, - 0xa2, 0x27, 0x27, 0x86, 0x98, 0xd1, 0x36, 0x69, 0x55, 0x1d, 0x13, 0xb0, 0x18, 0xee, 0xfa, 0xd4, - 0xde, 0xb4, 0xe4, 0xfb, 0x60, 0x91, 0x5e, 0x90, 0xfd, 0x8a, 0x89, 0x8d, 0x2e, 0x0e, 0xd2, 0xf4, - 0x54, 0x6b, 0x4b, 0x0d, 0xbc, 0xd8, 0xbf, 0xfd, 0xd8, 0xb0, 0x60, 0xbd, 0x61, 0xc1, 0xd7, 0x86, - 0x05, 0x6f, 0x39, 0xab, 0xad, 0x73, 0x56, 0xfb, 0xcc, 0x59, 0xed, 0xe5, 0x6a, 0x2a, 0xdd, 0x6c, - 0x31, 0xe9, 0xc6, 0x46, 0x47, 0xc5, 0x00, 0x7e, 0x8b, 0xd8, 0x28, 0xff, 0xe9, 0x94, 0x73, 0x2d, - 0xb7, 0x83, 0xb9, 0xd5, 0x1c, 0xed, 0xa4, 0xe9, 0x91, 0xeb, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xf8, 0xf7, 0xea, 0x0f, 0xd0, 0x01, 0x00, 0x00, + // 421 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0x41, 0x8b, 0xd3, 0x40, + 0x14, 0xc7, 0x13, 0x77, 0xad, 0xec, 0xc8, 0xa2, 0x3b, 0xee, 0x21, 0xee, 0x62, 0xba, 0xf4, 0x20, + 0x8b, 0xd0, 0xc4, 0x45, 0xf0, 0x03, 0x74, 0x03, 0x8b, 0xb2, 0x15, 0xa9, 0x7a, 0xf1, 0x32, 0xbc, + 0x4c, 0xc6, 0x74, 0x30, 0x93, 0x57, 0x32, 0xd3, 0xd0, 0x7e, 0x0b, 0x3f, 0x80, 0x1f, 0xc0, 0xa3, + 0x07, 0x3f, 0x44, 0x8f, 0xc5, 0x93, 0x78, 0x28, 0xd2, 0x1e, 0xfc, 0x1a, 0x92, 0x99, 0x10, 0x50, + 0x70, 0x2f, 0xc3, 0x7b, 0xff, 0xf7, 0xfb, 0xff, 0x0f, 0xf3, 0x1e, 0x09, 0xb5, 0xc8, 0x80, 0x4f, + 0x41, 0x96, 0xb1, 0x81, 0xa2, 0x58, 0xc6, 0xf5, 0x85, 0x2b, 0xa2, 0x59, 0x85, 0x06, 0x29, 0xed, + 0xe6, 0x91, 0x93, 0xeb, 0x8b, 0x93, 0x87, 0x1c, 0xb5, 0x42, 0xcd, 0x2c, 0x11, 0xbb, 0xc6, 0xe1, + 0x27, 0xc7, 0x39, 0xe6, 0xe8, 0xf4, 0xa6, 0x6a, 0xd5, 0x23, 0x50, 0xb2, 0xc4, 0xd8, 0xbe, 0x4e, + 0x1a, 0x7c, 0xde, 0x23, 0xbd, 0xd7, 0x50, 0x81, 0xd2, 0x74, 0x48, 0x1e, 0x28, 0x58, 0x30, 0x1b, + 0xcf, 0x72, 0xd0, 0xac, 0x90, 0x4a, 0x9a, 0xc0, 0x3f, 0xf3, 0xcf, 0xf7, 0x27, 0xf7, 0x15, 0x2c, + 0xde, 0x36, 0x93, 0x2b, 0xd0, 0xd7, 0x8d, 0x4e, 0x63, 0x72, 0xfc, 0x41, 0x16, 0x46, 0x54, 0x96, + 0xe5, 0xa8, 0x0d, 0x2b, 0xb1, 0x14, 0xc1, 0x2d, 0xcb, 0x1f, 0xb9, 0xd9, 0x15, 0xe8, 0x4b, 0xd4, + 0xe6, 0x15, 0x96, 0x82, 0x26, 0xa4, 0xff, 0xaf, 0x41, 0xcd, 0x0b, 0x23, 0x67, 0x85, 0x14, 0x15, + 0x53, 0x98, 0x89, 0x60, 0xcf, 0x7a, 0x4f, 0xff, 0xf2, 0x8e, 0x3b, 0x66, 0x8c, 0x99, 0xa0, 0x2f, + 0xc8, 0xe0, 0x86, 0x14, 0x6d, 0x32, 0x96, 0x89, 0x3a, 0xd8, 0xb7, 0x41, 0x8f, 0xfe, 0x13, 0xf4, + 0xc6, 0x64, 0x89, 0xa8, 0xe9, 0x80, 0x1c, 0x76, 0x19, 0x29, 0x68, 0x11, 0xdc, 0xb6, 0xae, 0xbb, + 0xb9, 0xe3, 0x47, 0xa0, 0x05, 0x7d, 0x4c, 0xee, 0x75, 0x0c, 0x47, 0xd5, 0x7c, 0x48, 0xcf, 0x52, + 0x87, 0x2d, 0x75, 0x69, 0x45, 0xfa, 0x8e, 0x90, 0x74, 0x5e, 0x95, 0xac, 0x02, 0x23, 0x31, 0xb8, + 0x73, 0xe6, 0x9f, 0x1f, 0x8c, 0x9e, 0xaf, 0x36, 0x7d, 0xef, 0xe7, 0xa6, 0x7f, 0xea, 0x56, 0xa3, + 0xb3, 0x8f, 0x91, 0xc4, 0x58, 0x81, 0x99, 0x46, 0xd7, 0x22, 0x07, 0xbe, 0x4c, 0x04, 0xff, 0xfe, + 0x6d, 0x48, 0xda, 0xcd, 0x25, 0x82, 0x7f, 0xf9, 0xfd, 0xf5, 0x89, 0x3f, 0x39, 0x68, 0x92, 0x26, + 0x4d, 0xd0, 0xe8, 0xe5, 0x6a, 0x1b, 0xfa, 0xeb, 0x6d, 0xe8, 0xff, 0xda, 0x86, 0xfe, 0xa7, 0x5d, + 0xe8, 0xad, 0x77, 0xa1, 0xf7, 0x63, 0x17, 0x7a, 0xef, 0x9f, 0xe6, 0xd2, 0x4c, 0xe7, 0x69, 0xc4, + 0x51, 0xc5, 0xcd, 0x6d, 0xd8, 0x75, 0x72, 0x2c, 0x6c, 0x33, 0x74, 0x97, 0xb4, 0x68, 0x6f, 0xc9, + 0x2c, 0x67, 0x42, 0xa7, 0x3d, 0x8b, 0x3c, 0xfb, 0x13, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x2c, 0x1a, + 0x14, 0x6b, 0x02, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -165,6 +179,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.BurnRatio.Size() + i -= size + if _, err := m.BurnRatio.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTally(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a if m.GasCostCommit != 0 { i = encodeVarintTally(dAtA, i, uint64(m.GasCostCommit)) i-- @@ -233,6 +257,8 @@ func (m *Params) Size() (n int) { if m.GasCostCommit != 0 { n += 1 + sovTally(uint64(m.GasCostCommit)) } + l = m.BurnRatio.Size() + n += 1 + l + sovTally(uint64(l)) return n } @@ -385,6 +411,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BurnRatio", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTally + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTally + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTally + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.BurnRatio.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTally(dAtA[iNdEx:])