diff --git a/x/datadeal/keeper/certificate.go b/x/datadeal/keeper/certificate.go index 19a0c83c..84f1a7c6 100644 --- a/x/datadeal/keeper/certificate.go +++ b/x/datadeal/keeper/certificate.go @@ -15,6 +15,10 @@ func (k Keeper) SubmitConsent(ctx sdk.Context, cert *types.Certificate) error { return sdkerrors.Wrapf(types.ErrSubmitConsent, err.Error()) } + if err := k.oracleKeeper.VerifyOracle(ctx, unsignedCert.OracleAddress); err != nil { + return sdkerrors.Wrapf(types.ErrSubmitConsent, err.Error()) + } + deal, err := k.GetDeal(ctx, unsignedCert.DealId) if err != nil { return sdkerrors.Wrapf(types.ErrSubmitConsent, "failed to get deal. %v", err) @@ -64,12 +68,23 @@ func (k Keeper) sendReward(ctx sdk.Context, deal *types.Deal, unsignedCert *type return fmt.Errorf("not enough balance in deal") } - // TODO calculate oracle commission + oracle, err := k.oracleKeeper.GetOracle(ctx, unsignedCert.OracleAddress) + if err != nil { + return fmt.Errorf("failed to get oracle. %w", err) + } + oracleCommissionRate := oracle.OracleCommissionRate - providerReward := sdk.NewCoin(assets.MicroMedDenom, pricePerData.TruncateInt()) + oracleReward := sdk.NewCoin(assets.MicroMedDenom, pricePerData.Mul(oracleCommissionRate).TruncateInt()) + providerReward := sdk.NewCoin(assets.MicroMedDenom, pricePerData.Mul(sdk.OneDec().Sub(oracleCommissionRate)).TruncateInt()) if err := k.bankKeeper.SendCoins(ctx, dealAccAddr, providerAccAddr, sdk.NewCoins(providerReward)); err != nil { return fmt.Errorf("failed to send reward to provider. %w", err) } + + // We already do oracle address verification above. + oracleAccAddr, _ := sdk.AccAddressFromBech32(unsignedCert.OracleAddress) + if err := k.bankKeeper.SendCoins(ctx, dealAccAddr, oracleAccAddr, sdk.NewCoins(oracleReward)); err != nil { + return fmt.Errorf("failed to send reward to oracle. %w", err) + } return nil } diff --git a/x/datadeal/keeper/certificate_test.go b/x/datadeal/keeper/certificate_test.go index 9d80bc6c..c2165689 100644 --- a/x/datadeal/keeper/certificate_test.go +++ b/x/datadeal/keeper/certificate_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "encoding/base64" + "fmt" "testing" "github.com/btcsuite/btcd/btcec" @@ -17,6 +18,8 @@ import ( type certificateTestSuite struct { dealTestSuite + uniqueID string + oracleAccPrivKey cryptotypes.PrivKey oracleAccPubKey cryptotypes.PubKey oracleAccAddr sdk.AccAddress @@ -41,6 +44,7 @@ func (suite *certificateTestSuite) BeforeTest(_, _ string) { suite.consumerAccAddr = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) suite.defaultFunds = sdk.NewCoins(sdk.NewCoin(assets.MicroMedDenom, sdk.NewInt(10000000000))) + suite.uniqueID = "uniqueID" suite.oracleAccPrivKey = secp256k1.GenPrivKey() suite.oracleAccPubKey = suite.oracleAccPrivKey.PubKey() suite.oracleAccAddr = sdk.AccAddress(suite.oracleAccPubKey.Address()) @@ -59,7 +63,7 @@ func (suite *certificateTestSuite) BeforeTest(_, _ string) { suite.OracleKeeper.SetParams(suite.Ctx, oracletypes.Params{ OraclePublicKey: base64.StdEncoding.EncodeToString(suite.oraclePubKey.SerializeCompressed()), OraclePubKeyRemoteReport: "", - UniqueId: "", + UniqueId: suite.uniqueID, }) err := suite.DataDealKeeper.SetNextDealNumber(suite.Ctx, 1) @@ -85,12 +89,28 @@ func (suite *certificateTestSuite) createSampleDeal(budgetAmount, maxNumData uin return dealID } +func (suite *certificateTestSuite) storeSampleOracle(address, uniqueID string, commissionRate sdk.Dec) *oracletypes.Oracle { + oracle := &oracletypes.Oracle{ + OracleAddress: address, + UniqueId: uniqueID, + Endpoint: "https://my-validator.org", + OracleCommissionRate: commissionRate, + } + err := suite.OracleKeeper.SetOracle(suite.Ctx, oracle) + suite.Require().NoError(err) + + return oracle +} + func (suite *certificateTestSuite) TestSubmitConsentSuccess() { budgetAmount := uint64(10000) dealID := suite.createSampleDeal(budgetAmount, 10) deal, err := suite.DataDealKeeper.GetDeal(suite.Ctx, dealID) suite.Require().NoError(err) + oracleCommissionRate := sdk.NewDecWithPrec(1, 1) // 10% + suite.storeSampleOracle(suite.oracleAccAddr.String(), suite.uniqueID, oracleCommissionRate) + unsignedCert := &types.UnsignedCertificate{ Cid: "cid", OracleAddress: suite.oracleAccAddr.String(), @@ -114,6 +134,9 @@ func (suite *certificateTestSuite) TestSubmitConsentSuccess() { providerBalance := suite.BankKeeper.GetBalance(suite.Ctx, suite.providerAccAddr, assets.MicroMedDenom) suite.Require().Equal(sdk.ZeroInt(), providerBalance.Amount) + oracleBalance := suite.BankKeeper.GetBalance(suite.Ctx, suite.oracleAccAddr, assets.MicroMedDenom) + suite.Require().Equal(sdk.ZeroInt(), oracleBalance.Amount) + dealAccAddr, err := sdk.AccAddressFromBech32(deal.Address) suite.Require().NoError(err) dealBalance := suite.BankKeeper.GetBalance(suite.Ctx, dealAccAddr, assets.MicroMedDenom) @@ -123,7 +146,10 @@ func (suite *certificateTestSuite) TestSubmitConsentSuccess() { suite.Require().NoError(err) providerBalance = suite.BankKeeper.GetBalance(suite.Ctx, suite.providerAccAddr, assets.MicroMedDenom) - suite.Require().Equal(sdk.NewInt(1000), providerBalance.Amount) + suite.Require().Equal(sdk.NewInt(900), providerBalance.Amount) + + oracleBalance = suite.BankKeeper.GetBalance(suite.Ctx, suite.oracleAccAddr, assets.MicroMedDenom) + suite.Require().Equal(sdk.NewInt(100), oracleBalance.Amount) dealAccAddr, err = sdk.AccAddressFromBech32(deal.Address) suite.Require().NoError(err) @@ -142,6 +168,9 @@ func (suite *certificateTestSuite) TestSubmitConsentChangeStatusComplete() { deal, err := suite.DataDealKeeper.GetDeal(suite.Ctx, dealID) suite.Require().NoError(err) + oracleCommissionRate := sdk.NewDecWithPrec(1, 1) // 10% + suite.storeSampleOracle(suite.oracleAccAddr.String(), suite.uniqueID, oracleCommissionRate) + unsignedCert := &types.UnsignedCertificate{ Cid: "cid", OracleAddress: suite.oracleAccAddr.String(), @@ -164,6 +193,9 @@ func (suite *certificateTestSuite) TestSubmitConsentChangeStatusComplete() { providerBalance := suite.BankKeeper.GetBalance(suite.Ctx, suite.providerAccAddr, assets.MicroMedDenom) suite.Require().Equal(sdk.ZeroInt(), providerBalance.Amount) + oracleBalance := suite.BankKeeper.GetBalance(suite.Ctx, suite.oracleAccAddr, assets.MicroMedDenom) + suite.Require().Equal(sdk.ZeroInt(), oracleBalance.Amount) + dealAccAddr, err := sdk.AccAddressFromBech32(deal.Address) suite.Require().NoError(err) dealBalance := suite.BankKeeper.GetBalance(suite.Ctx, dealAccAddr, assets.MicroMedDenom) @@ -173,7 +205,10 @@ func (suite *certificateTestSuite) TestSubmitConsentChangeStatusComplete() { suite.Require().NoError(err) providerBalance = suite.BankKeeper.GetBalance(suite.Ctx, suite.providerAccAddr, assets.MicroMedDenom) - suite.Require().Equal(sdk.NewInt(10000), providerBalance.Amount) + suite.Require().Equal(sdk.NewInt(9000), providerBalance.Amount) + + oracleBalance = suite.BankKeeper.GetBalance(suite.Ctx, suite.oracleAccAddr, assets.MicroMedDenom) + suite.Require().Equal(sdk.NewInt(1000), oracleBalance.Amount) dealAccAddr, err = sdk.AccAddressFromBech32(deal.Address) suite.Require().NoError(err) @@ -186,10 +221,72 @@ func (suite *certificateTestSuite) TestSubmitConsentChangeStatusComplete() { suite.Require().Equal(types.DEAL_STATUS_COMPLETED, deal.Status) } +func (suite *certificateTestSuite) TestSubmitConsentNotRegisteredOracle() { + budgetAmount := uint64(10000) + dealID := suite.createSampleDeal(budgetAmount, 1) + + unsignedCert := &types.UnsignedCertificate{ + Cid: "cid", + OracleAddress: suite.providerAccAddr.String(), + DealId: dealID, + ProviderAddress: suite.providerAccAddr.String(), + DataHash: suite.dataHash, + } + + unsignedCertBz, err := unsignedCert.Marshal() + suite.Require().NoError(err) + + sign, err := suite.oraclePrivKey.Sign(unsignedCertBz) + suite.Require().NoError(err) + + certificate := &types.Certificate{ + UnsignedCertificate: unsignedCert, + Signature: sign.Serialize(), + } + + err = suite.DataDealKeeper.SubmitConsent(suite.Ctx, certificate) + suite.Require().ErrorIs(err, types.ErrSubmitConsent) + suite.Require().ErrorContains(err, fmt.Sprintf("failed to oracle validation. address(%s)", suite.providerAccAddr.String())) +} + +func (suite *certificateTestSuite) TestSubmitConsentNotSameUniqueID() { + budgetAmount := uint64(10000) + dealID := suite.createSampleDeal(budgetAmount, 1) + + oracleCommissionRate := sdk.NewDecWithPrec(1, 1) // 10% + suite.storeSampleOracle(suite.oracleAccAddr.String(), "invalidUniqueID", oracleCommissionRate) + + unsignedCert := &types.UnsignedCertificate{ + Cid: "cid", + OracleAddress: suite.oracleAccAddr.String(), + DealId: dealID, + ProviderAddress: suite.providerAccAddr.String(), + DataHash: suite.dataHash, + } + + unsignedCertBz, err := unsignedCert.Marshal() + suite.Require().NoError(err) + + sign, err := suite.oraclePrivKey.Sign(unsignedCertBz) + suite.Require().NoError(err) + + certificate := &types.Certificate{ + UnsignedCertificate: unsignedCert, + Signature: sign.Serialize(), + } + + err = suite.DataDealKeeper.SubmitConsent(suite.Ctx, certificate) + suite.Require().ErrorIs(err, types.ErrSubmitConsent) + suite.Require().ErrorContains(err, "is not active an oracle.") +} + func (suite *certificateTestSuite) TestSubmitConsentInvalidSignature() { budgetAmount := uint64(10000) dealID := suite.createSampleDeal(budgetAmount, 1) + oracleCommissionRate := sdk.NewDecWithPrec(1, 1) // 10% + suite.storeSampleOracle(suite.oracleAccAddr.String(), suite.uniqueID, oracleCommissionRate) + unsignedCert := &types.UnsignedCertificate{ Cid: "cid", OracleAddress: suite.oracleAccAddr.String(), @@ -215,6 +312,9 @@ func (suite *certificateTestSuite) TestSubmitConsentInvalidSignature() { } func (suite *certificateTestSuite) TestSubmitConsentNotExistDeal() { + oracleCommissionRate := sdk.NewDecWithPrec(1, 1) // 10% + suite.storeSampleOracle(suite.oracleAccAddr.String(), suite.uniqueID, oracleCommissionRate) + unsignedCert := &types.UnsignedCertificate{ Cid: "cid", OracleAddress: suite.oracleAccAddr.String(), diff --git a/x/oracle/keeper/keeper.go b/x/oracle/keeper/keeper.go index b836d1ff..1fddc214 100644 --- a/x/oracle/keeper/keeper.go +++ b/x/oracle/keeper/keeper.go @@ -61,4 +61,22 @@ func (k Keeper) VerifyOracleSignature(ctx sdk.Context, msg codec.ProtoMarshaler, } return nil -} \ No newline at end of file +} + +func (k Keeper) VerifyOracle(ctx sdk.Context, oracleAddress string) error { + oracle, err := k.GetOracle(ctx, oracleAddress) + if err != nil { + return fmt.Errorf("failed to oracle validation. address(%s) %w", oracleAddress, err) + } + + activeUniqueID := k.GetParams(ctx).UniqueId + if activeUniqueID != oracle.UniqueId { + return fmt.Errorf("is not active an oracle. oracleAddress(%s), oracleUniqueID(%s), activeUniqueID(%s)", + oracle.OracleAddress, + oracle.UniqueId, + activeUniqueID, + ) + } + + return nil +} diff --git a/x/oracle/types/params.go b/x/oracle/types/params.go index b391fb8d..6951d47a 100644 --- a/x/oracle/types/params.go +++ b/x/oracle/types/params.go @@ -50,6 +50,14 @@ func (p *Params) Validate() error { return nil } +func mustDecodeBase64Str(s string) []byte { + decoded, err := base64.StdEncoding.DecodeString(s) + if err != nil { + panic(err) + } + return decoded +} + func validateOraclePublicKey(i interface{}) error { pubKeyBase64, ok := i.(string) if !ok { @@ -98,12 +106,4 @@ func validateUniqueID(i interface{}) error { // It panics if the decoding is failed, assuming that the Params was already validated by Params.Validate(). func (p Params) MustDecodeOraclePublicKey() []byte { return mustDecodeBase64Str(p.OraclePublicKey) -} - -func mustDecodeBase64Str(s string) []byte { - decoded, err := base64.StdEncoding.DecodeString(s) - if err != nil { - panic(err) - } - return decoded -} +} \ No newline at end of file