Skip to content

Commit

Permalink
[Utility] Feat: Chain-specific compute unit to tokens multipliers (#552)
Browse files Browse the repository at this point in the history
- Added a `compute_units_per_relay` param to new services
- Updated `SettleSessionAccounting()` to compute units per relay along with the general compute units to tokens multipliers
- Added new tests.
- Updated existing tests
- Added comments where necessary
  • Loading branch information
rBurgett authored Jul 24, 2024
1 parent 4fcd63e commit 0cecfbc
Show file tree
Hide file tree
Showing 23 changed files with 501 additions and 158 deletions.
166 changes: 113 additions & 53 deletions api/poktroll/shared/service.pulsar.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,11 @@ genesis:
- service:
id: anvil
name: ""
compute_units_per_relay: 1
- service:
id: ollama
name: ""
compute_units_per_relay: 1
stake:
# NB: This value should be exactly 1upokt smaller than the value in
# `supplier1_stake_config.yaml` so that the stake command causes a state change.
Expand Down
1 change: 1 addition & 0 deletions e2e/tests/0_settlement.feature
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Feature: Tokenomics Namespace
And the "supplier" account for "supplier1" is staked
And an account exists for "app1"
And the "application" account for "app1" is staked
And the service "anvil" registered for application "app1" has a compute units per relay of "1"
When the supplier "supplier1" has serviced a session with "10" relays for service "anvil" for application "app1"
And the user should wait for the "proof" module "CreateClaim" Message to be submitted
And the user should wait for the "proof" module "SubmitProof" Message to be submitted
Expand Down
15 changes: 15 additions & 0 deletions e2e/tests/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,21 @@ func (s *suite) TheAccountForIsStaked(actorType, accName string) {
s.scenarioState[accStakeKey(actorType, accName)] = stakeAmount // save the stakeAmount for later
}

func (s *suite) TheServiceRegisteredForApplicationHasAComputeUnitsPerRelayOf(serviceId string, appName string, cuprStr string) {
app, ok := accNameToAppMap[appName]
require.True(s, ok, "application %s not found", appName)

for _, serviceConfig := range app.ServiceConfigs {
if serviceConfig.Service.Id == serviceId {
cupr, err := strconv.ParseUint(cuprStr, 10, 64)
require.NoError(s, err)
require.Equal(s, cupr, serviceConfig.Service.ComputeUnitsPerRelay)
return
}
}
s.Fatalf("ERROR: service %s is not registered for application %s", serviceId, appName)
}

func (s *suite) TheForAccountIsNotStaked(actorType, accName string) {
_, ok := s.getStakedAmount(actorType, accName)
require.Falsef(s, ok, "account %s of type %s SHOULD NOT be staked", accName, actorType)
Expand Down
3 changes: 3 additions & 0 deletions proto/poktroll/shared/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ message Service {

// TODO_BETA: Name is currently unused but acts as a reminder that an optional onchain representation of the service is necessary
string name = 2; // (Optional) Semantic human readable name for the service

// Used alongside the global 'compute_units_to_tokens_multipler' to calculate the cost of a relay for this service
uint64 compute_units_per_relay = 3; // Compute units required per relay for this service
}

// ApplicationServiceConfig holds the service configuration the application stakes for
Expand Down
13 changes: 11 additions & 2 deletions testutil/keeper/tokenomics.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@ type TokenomicsKeepersOpt func(context.Context, *TokenomicsModuleKeepers) contex

func TokenomicsKeeper(t testing.TB) (tokenomicsKeeper tokenomicskeeper.Keeper, ctx context.Context) {
t.Helper()
k, ctx, _, _ := TokenomicsKeeperWithActorAddrs(t)
k, ctx, _, _ := TokenomicsKeeperWithActorAddrs(t, nil)
return k, ctx
}

// TODO_TECHDEBT: Have the callers use the keepers to find `appAddr` and `supplierAddr`
// rather than returning them explicitly.
func TokenomicsKeeperWithActorAddrs(t testing.TB) (
// TODO_TECHDEBT(@Olshansk): Remove `service` parameter and convert proper options.
func TokenomicsKeeperWithActorAddrs(t testing.TB, service *sharedtypes.Service) (
tokenomicsKeeper tokenomicskeeper.Keeper,
ctx context.Context,
appAddr string,
Expand Down Expand Up @@ -106,6 +107,14 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) (
Stake: &sdk.Coin{Denom: "upokt", Amount: math.NewInt(100000)},
}

if service != nil {
application.ServiceConfigs = []*sharedtypes.ApplicationServiceConfig{
{
Service: service,
},
}
}

// Prepare the test supplier.
supplier := sharedtypes.Supplier{
Address: sample.AccAddress(),
Expand Down
14 changes: 9 additions & 5 deletions testutil/proof/fixture_generators.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ import (
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
)

const (
// DefaultTestServiceID is the default test service ID
DefaultTestServiceID = "svc1"
)

// BaseClaim returns a base (default, example, etc..) claim with the given app
// address, supplier address, and sum that can be used for testing.
func BaseClaim(appAddr, supplierAddr string, sum uint64) prooftypes.Claim {
// address, supplier address, sum, and serviceID that can be used for testing.
func BaseClaim(appAddr, supplierAddr string, sum uint64, serviceId string) prooftypes.Claim {
return prooftypes.Claim{
SupplierAddress: supplierAddr,
SessionHeader: &sessiontypes.SessionHeader{
ApplicationAddress: appAddr,
Service: &sharedtypes.Service{
Id: "svc1",
Name: "svcName1",
Id: serviceId,
},
SessionId: "session_id",
SessionStartBlockHeight: 1,
Expand All @@ -40,7 +44,7 @@ func BaseClaim(appAddr, supplierAddr string, sum uint64) prooftypes.Claim {
// generated this way will have a random chance to require a proof via probabilistic
// selection.
func ClaimWithRandomHash(t *testing.T, appAddr, supplierAddr string, sum uint64) prooftypes.Claim {
claim := BaseClaim(appAddr, supplierAddr, sum)
claim := BaseClaim(appAddr, supplierAddr, sum, DefaultTestServiceID)
claim.RootHash = RandSmstRootWithSum(t, sum)
return claim
}
Expand Down
21 changes: 17 additions & 4 deletions x/service/keeper/msg_server_add_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ func TestMsgServer_AddService(t *testing.T) {

// Declare test services
svc1 := sharedtypes.Service{
Id: "svc1",
Name: "service 1",
Id: "svc1",
Name: "service 1",
ComputeUnitsPerRelay: 1,
}

preExistingService := sharedtypes.Service{
Id: "svc2",
Name: "service 2",
Id: "svc2",
Name: "service 2",
ComputeUnitsPerRelay: 1,
}

// Generate a valid address
Expand Down Expand Up @@ -125,6 +127,17 @@ func TestMsgServer_AddService(t *testing.T) {
},
expectedErr: types.ErrServiceMissingName,
},
{
desc: "invalid - zero compute units per relay",
setup: func(t *testing.T) {},
address: sample.AccAddress(),
service: sharedtypes.Service{
Id: "svc1",
Name: "service 1",
ComputeUnitsPerRelay: 0,
},
expectedErr: types.ErrServiceInvalidComputUnitsPerRelay,
},
{
desc: "invalid - service already exists (same service supplier)",
setup: func(t *testing.T) {},
Expand Down
23 changes: 20 additions & 3 deletions x/service/module/tx_add_service.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package service

// TODO_BETA: Add `UpdateService` or modify `AddService` to `UpsertService` to allow service owners
// to update parameters of existing services. This will requiring updating `proto/poktroll/service/tx.proto` and
// all downstream code paths.
import (
"fmt"
"strconv"

"github.com/cosmos/cosmos-sdk/client"
Expand All @@ -13,16 +17,17 @@ import (

var _ = strconv.Itoa(0)

// TODO_MAINNET: Make it possible to update a service (e.g. update # of compute units per relay
func CmdAddService() *cobra.Command {
cmd := &cobra.Command{
Use: "add-service <service_id> <service_name>",
Use: fmt.Sprintf("add-service <service_id> <service_name> [compute_units_per_relay: default={%d}]", types.DefaultComputeUnitsPerRelay),
Short: "Add a new service to the network",
Long: `Add a new service to the network that will be available for applications,
gateways and suppliers to use. The service id MUST be unique but the service name doesn't have to be.
Example:
$ poktrolld tx service add-service "svc1" "service_one" --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) --home $(POKTROLLD_HOME)`,
Args: cobra.ExactArgs(2),
$ poktrolld tx service add-service "svc1" "service_one" 1 --keyring-backend test --from $(SUPPLIER) --node $(POCKET_NODE) --home $(POKTROLLD_HOME)`,
Args: cobra.MinimumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) (err error) {
serviceIdStr := args[0]
serviceNameStr := args[1]
Expand All @@ -32,10 +37,22 @@ $ poktrolld tx service add-service "svc1" "service_one" --keyring-backend test -
return err
}

computeUnitsPerRelay := types.DefaultComputeUnitsPerRelay
// if compute units per relay argument is provided
if len(args) > 2 {
computeUnitsPerRelay, err = strconv.ParseUint(args[2], 10, 64)
if err != nil {
return types.ErrServiceInvalidComputUnitsPerRelay.Wrapf("unable to parse as uint64: %s", args[2])
}
} else {
fmt.Printf("Using default compute_units_per_relay: %d\n", types.DefaultComputeUnitsPerRelay)
}

msg := types.NewMsgAddService(
clientCtx.GetFromAddress().String(),
serviceIdStr,
serviceNameStr,
computeUnitsPerRelay,
)
if err := msg.ValidateBasic(); err != nil {
return err
Expand Down
31 changes: 24 additions & 7 deletions x/service/module/tx_add_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package service_test

import (
"fmt"
"strconv"
"testing"

sdkerrors "cosmossdk.io/errors"
Expand Down Expand Up @@ -52,12 +53,14 @@ func TestCLI_AddService(t *testing.T) {

// Prepare two valid services
svc1 := sharedtypes.Service{
Id: "svc1",
Name: "service name",
Id: "svc1",
Name: "service name",
ComputeUnitsPerRelay: 1,
}
svc2 := sharedtypes.Service{
Id: "svc2",
Name: "service name 2",
Id: "svc2",
Name: "service name 2",
ComputeUnitsPerRelay: 1,
}
// Add svc2 to the network
args := []string{
Expand All @@ -81,6 +84,15 @@ func TestCLI_AddService(t *testing.T) {
supplierAddress: account.Address.String(),
service: svc1,
},
{
desc: "valid - add new service without specifying compute units per relay so that it uses the default",
supplierAddress: account.Address.String(),
service: sharedtypes.Service{
Id: svc1.Id,
Name: svc1.Name,
ComputeUnitsPerRelay: 0, // this parameter is omitted when the test is run
},
},
{
desc: "invalid - missing service id",
supplierAddress: account.Address.String(),
Expand Down Expand Up @@ -114,12 +126,17 @@ func TestCLI_AddService(t *testing.T) {
require.NoError(t, net.WaitForNextBlock())

// Prepare the arguments for the CLI command
args := []string{
argsAndFlags := []string{
test.service.Id,
test.service.Name,
fmt.Sprintf("--%s=%s", flags.FlagFrom, test.supplierAddress),
}
args = append(args, commonArgs...)
if test.service.ComputeUnitsPerRelay > 0 {
// Only include compute units per relay argument if provided
argsAndFlags = append(argsAndFlags, strconv.FormatUint(test.service.ComputeUnitsPerRelay, 10))
}
argsAndFlags = append(argsAndFlags, fmt.Sprintf("--%s=%s", flags.FlagFrom, test.supplierAddress))

args := append(argsAndFlags, commonArgs...)

// Execute the command
addServiceOutput, err := clitestutil.ExecTestCLICmd(ctx, service.CmdAddService(), args)
Expand Down
25 changes: 13 additions & 12 deletions x/service/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ import sdkerrors "cosmossdk.io/errors"

// x/service module sentinel errors
var (
ErrServiceInvalidSigner = sdkerrors.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
ErrServiceDuplicateIndex = sdkerrors.Register(ModuleName, 1101, "duplicate index when adding a new service")
ErrServiceInvalidAddress = sdkerrors.Register(ModuleName, 1102, "invalid address when adding a new service")
ErrServiceMissingID = sdkerrors.Register(ModuleName, 1103, "missing service ID")
ErrServiceMissingName = sdkerrors.Register(ModuleName, 1104, "missing service name")
ErrServiceAlreadyExists = sdkerrors.Register(ModuleName, 1105, "service already exists")
ErrServiceInvalidServiceFee = sdkerrors.Register(ModuleName, 1106, "invalid ServiceFee")
ErrServiceAccountNotFound = sdkerrors.Register(ModuleName, 1107, "account not found")
ErrServiceNotEnoughFunds = sdkerrors.Register(ModuleName, 1108, "not enough funds to add service")
ErrServiceFailedToDeductFee = sdkerrors.Register(ModuleName, 1109, "failed to deduct fee")
ErrServiceInvalidRelayResponse = sdkerrors.Register(ModuleName, 1110, "invalid relay response")
ErrServiceInvalidRelayRequest = sdkerrors.Register(ModuleName, 1111, "invalid relay request")
ErrServiceInvalidSigner = sdkerrors.Register(ModuleName, 1100, "expected gov account as only signer for proposal message")
ErrServiceDuplicateIndex = sdkerrors.Register(ModuleName, 1101, "duplicate index when adding a new service")
ErrServiceInvalidAddress = sdkerrors.Register(ModuleName, 1102, "invalid address when adding a new service")
ErrServiceMissingID = sdkerrors.Register(ModuleName, 1103, "missing service ID")
ErrServiceMissingName = sdkerrors.Register(ModuleName, 1104, "missing service name")
ErrServiceAlreadyExists = sdkerrors.Register(ModuleName, 1105, "service already exists")
ErrServiceInvalidServiceFee = sdkerrors.Register(ModuleName, 1106, "invalid ServiceFee")
ErrServiceAccountNotFound = sdkerrors.Register(ModuleName, 1107, "account not found")
ErrServiceNotEnoughFunds = sdkerrors.Register(ModuleName, 1108, "not enough funds to add service")
ErrServiceFailedToDeductFee = sdkerrors.Register(ModuleName, 1109, "failed to deduct fee")
ErrServiceInvalidRelayResponse = sdkerrors.Register(ModuleName, 1110, "invalid relay response")
ErrServiceInvalidRelayRequest = sdkerrors.Register(ModuleName, 1111, "invalid relay request")
ErrServiceInvalidComputUnitsPerRelay = sdkerrors.Register(ModuleName, 1112, "invalid compute units per relay")
)
33 changes: 27 additions & 6 deletions x/service/types/message_add_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@ package types

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/pokt-network/poktroll/x/shared/types"
)

const (
DefaultComputeUnitsPerRelay uint64 = 1
// ComputeUnitsPerRelayMax is the maximum allowed compute_units_per_relay value when adding or updating a service.
// TODO_MAINNET: The reason we have a maximum is to account for potential integer overflows. This is
// something that needs to be revisited or reconsidered prior to mainnet.
ComputeUnitsPerRelayMax uint64 = 2 ^ 16
)

var _ sdk.Msg = (*MsgAddService)(nil)

func NewMsgAddService(address, serviceId, serviceName string) *MsgAddService {
func NewMsgAddService(address, serviceId, serviceName string, computeUnitsPerRelay uint64) *MsgAddService {
return &MsgAddService{
Address: address,
Service: types.Service{
Id: serviceId,
Name: serviceName,
},
Service: *types.NewService(
serviceId,
serviceName,
computeUnitsPerRelay,
),
}
}

Expand All @@ -30,5 +38,18 @@ func (msg *MsgAddService) ValidateBasic() error {
if msg.Service.Name == "" {
return ErrServiceMissingName
}
if err := ValidateComputeUnitsPerRelay(msg.Service.ComputeUnitsPerRelay); err != nil {
return err
}
return nil
}

// ValidateComputeUnitsPerRelay makes sure the compute units per relay is a valid value
func ValidateComputeUnitsPerRelay(computeUnitsPerRelay uint64) error {
if computeUnitsPerRelay == 0 {
return ErrServiceInvalidComputUnitsPerRelay.Wrap("compute units per relay must be greater than 0")
} else if computeUnitsPerRelay > ComputeUnitsPerRelayMax {
return ErrServiceInvalidComputUnitsPerRelay.Wrapf("compute units per relay must be less than %d", ComputeUnitsPerRelayMax)
}
return nil
}
Loading

0 comments on commit 0cecfbc

Please sign in to comment.