Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Data Oracles #388

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions proto/canine_chain/storage/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "canine_chain/storage/params.proto";
import "canine_chain/storage/active_deals.proto";
import "canine_chain/storage/providers.proto";
import "canine_chain/storage/payment_info.proto";
import "canine_chain/storage/oracle.proto";

option go_package = "github.com/jackalLabs/canine-chain/x/storage/types";

Expand All @@ -18,4 +19,6 @@ message GenesisState {
repeated ActiveProviders active_providers_list = 6 [ (gogoproto.nullable) = false ];
repeated ReportForm report_forms = 7 [ (gogoproto.nullable) = false ];
repeated AttestationForm attest_forms = 8 [ (gogoproto.nullable) = false ];
repeated OracleRequest oracle_requests = 9 [ (gogoproto.nullable) = false ];
repeated OracleEntry oracle_entries = 10 [ (gogoproto.nullable) = false ];
}
22 changes: 22 additions & 0 deletions proto/canine_chain/storage/oracle.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto3";
package canine_chain.storage;

import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/jackalLabs/canine-chain/x/storage/types";


message OracleRequest {
string requester = 1;
bytes merkle = 2; // file identifier that we want
int64 chunk = 3; // the chunk we want to have accessible on chain
cosmos.base.v1beta1.Coin bid = 4 [ (gogoproto.nullable) = false ];
}

message OracleEntry {
string owner = 1;
bytes merkle = 2; // file identifier that we want
int64 chunk = 3; // the chunk we want to have accessible on chain
bytes data = 4; // the data from the file
}
27 changes: 27 additions & 0 deletions proto/canine_chain/storage/tx.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
syntax = "proto3";
package canine_chain.storage;

import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/jackalLabs/canine-chain/x/storage/types";

service Msg {
Expand All @@ -21,6 +24,10 @@ service Msg {
rpc Attest(MsgAttest) returns (MsgAttestResponse);
rpc RequestReportForm(MsgRequestReportForm) returns (MsgRequestReportFormResponse);
rpc Report(MsgReport) returns (MsgReportResponse);

// oracle stuff
rpc RequestChunk(MsgRequestChunk) returns (MsgRequestChunkResponse);
rpc FulfillRequest(MsgFulfillRequest) returns (MsgFulfillRequestResponse);
}

message MsgPostFile {
Expand Down Expand Up @@ -168,3 +175,23 @@ message MsgReport {
}

message MsgReportResponse {}

message MsgRequestChunk {
string creator = 1;
bytes merkle = 2;
int64 chunk = 3;
cosmos.base.v1beta1.Coin bid = 4 [ (gogoproto.nullable) = false ];
}

message MsgRequestChunkResponse {}

message MsgFulfillRequest {
string creator = 1;
string requester = 2;
bytes merkle = 3;
int64 chunk = 4;
bytes data = 5;
bytes hash_list = 6;
}

message MsgFulfillRequestResponse {}
12 changes: 12 additions & 0 deletions x/storage/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState)
k.SetActiveProviders(ctx, elem)
}

// Set all the oracle requests
for _, elem := range genState.OracleRequests {
k.SetOracleRequest(ctx, elem)
}

// Set all the oracle entries
for _, elem := range genState.OracleEntries {
k.SetOracleEntry(ctx, elem)
}

k.SetParams(ctx, genState.Params)
}

Expand All @@ -58,6 +68,8 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState {
genesis.ActiveProvidersList = k.GetAllActiveProviders(ctx)
genesis.ReportForms = k.GetAllReport(ctx)
genesis.AttestForms = k.GetAllAttestation(ctx)
genesis.OracleRequests = k.GetAllOracleRequests(ctx)
genesis.OracleEntries = k.GetAllOracleEntries(ctx)

return genesis
}
7 changes: 6 additions & 1 deletion x/storage/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgBuyStorage:
res, err := msgServer.BuyStorage(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)

case *types.MsgAddClaimer:
res, err := msgServer.AddProviderClaimer(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
Expand All @@ -63,6 +62,12 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgReport:
res, err := msgServer.Report(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgRequestChunk:
res, err := msgServer.RequestChunk(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgFulfillRequest:
res, err := msgServer.FulfillRequest(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:
errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg)
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
Expand Down
104 changes: 104 additions & 0 deletions x/storage/keeper/msg_server_oracle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package keeper

import (
"context"
"encoding/hex"

"github.com/jackalLabs/canine-chain/v3/x/storage/utils"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerr "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/jackalLabs/canine-chain/v3/x/storage/types"
)

func MakeAddress() (sdk.AccAddress, error) {
oracleAddressString := "storage_oracle"
oracleAddressHex := hex.EncodeToString([]byte(oracleAddressString))
return sdk.AccAddressFromHex(oracleAddressHex)
}

func (k msgServer) RequestChunk(goCtx context.Context, msg *types.MsgRequestChunk) (*types.MsgRequestChunkResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

bid := msg.Bid
bidAsCoins := sdk.NewCoins(bid)

creatorAddress, err := sdk.AccAddressFromBech32(msg.Creator)
if err != nil {
return nil, sdkerr.Wrapf(err, "cannot get address from %s", msg.Creator)
}

oracleAddress, err := MakeAddress()
if err != nil {
return nil, sdkerr.Wrap(err, "could not make oracle address")
}

err = k.bankkeeper.SendCoinsFromAccountToModule(ctx, creatorAddress, types.ModuleName, bidAsCoins)
if err != nil {
return nil, sdkerr.Wrap(err, "user does not have enough coins")
}
err = k.bankkeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, oracleAddress, bidAsCoins)
if err != nil {
return nil, sdkerr.Wrap(err, "somehow the module itself ate all our coins, we need to fail here")
}

req := types.OracleRequest{
Requester: msg.Creator,
Merkle: msg.Merkle,
Chunk: msg.Chunk,
Bid: bid,
}

k.SetOracleRequest(ctx, req)

return &types.MsgRequestChunkResponse{}, nil
}

func (k msgServer) FulfillRequest(goCtx context.Context, msg *types.MsgFulfillRequest) (*types.MsgFulfillRequestResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

verified := utils.VerifyProof(msg.Merkle, msg.HashList, msg.Chunk, msg.Data)
if !verified {
return nil, sdkerr.Wrap(sdkerr.ErrUnauthorized, "cannot verify data against merkle")
}

req, found := k.GetOracleRequest(ctx, msg.Requester, msg.Merkle, msg.Chunk)
if !found {
return nil, sdkerr.Wrap(sdkerr.ErrNotFound, "cannot find oracle request")
}

bid := req.Bid
bidAsCoins := sdk.NewCoins(bid)

oracleAddress, err := MakeAddress()
if err != nil {
return nil, sdkerr.Wrap(err, "could not make oracle address")
}
creatorAddress, err := sdk.AccAddressFromBech32(msg.Creator)
if err != nil {
return nil, sdkerr.Wrapf(err, "could not make user address from %s", msg.Creator)
}

err = k.bankkeeper.SendCoinsFromAccountToModule(ctx, oracleAddress, types.ModuleName, bidAsCoins)
if err != nil {
return nil, sdkerr.Wrap(err, "oracle somehow does not have enough coins")
}
err = k.bankkeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, creatorAddress, bidAsCoins)
if err != nil {
return nil, sdkerr.Wrap(err, "somehow the module itself ate all our coins, we need to fail here")
}

entry := types.OracleEntry{
Owner: msg.Requester,
Merkle: msg.Merkle,
Chunk: msg.Chunk,
Data: msg.Data,
}

k.SetOracleEntry(ctx, entry)

k.RemoveOracleRequest(ctx, msg.Requester, msg.Merkle, msg.Chunk)

return &types.MsgFulfillRequestResponse{}, nil
}
130 changes: 130 additions & 0 deletions x/storage/keeper/msg_server_oracle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package keeper_test

import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/jackalLabs/canine-chain/v3/testutil"
"github.com/jackalLabs/canine-chain/v3/x/storage/keeper"
"github.com/jackalLabs/canine-chain/v3/x/storage/types"
"github.com/jackalLabs/canine-chain/v3/x/storage/utils"

"github.com/stretchr/testify/require"
)

func TestMakeAddress(t *testing.T) {
address, err := keeper.MakeAddress()
require.NoError(t, err)
t.Log(address.String())
}

func (suite *KeeperTestSuite) TestRequestAndFulfillOracle() {
suite.SetupSuite()
msgSrvr, k, ctx := setupMsgServer(suite)

replicaFileData := []byte("this is test data for a tree I will be testing against. BLOCKCHAIN AND STORAGE NETWORK DELIVERING ON-CHAIN ACCESS TO DATA STORAGE EVERYWHERE.")
fileData := []byte("this is test data for a tree I will be testing against. BLOCKCHAIN AND STORAGE NETWORK DELIVERING ON-CHAIN ACCESS TO DATA STORAGE EVERYWHERE.")
buf := bytes.NewBuffer(fileData)
var chunkSize int64 = 2
tree, _, _, err := utils.BuildJustTree(buf, chunkSize)
suite.Require().NoError(err)

suite.T().Log(tree.Data)

testAddresses, err := testutil.CreateTestAddresses("cosmos", 3)
suite.Require().NoError(err)

testAccount := testAddresses[0]
testOracleAccount := testAddresses[2]
depoAccount := testAddresses[1]

coins := sdk.NewCoins(sdk.NewCoin("ujkl", sdk.NewInt(100000000000))) // Send some coins to their account
testAcc, _ := sdk.AccAddressFromBech32(testAccount)
err = suite.bankKeeper.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, testAcc, coins)
suite.Require().NoError(err)

suite.storageKeeper.SetParams(suite.ctx, types.Params{
DepositAccount: depoAccount,
ProofWindow: 50,
ChunkSize: chunkSize,
PriceFeed: "jklprice",
MissesToBurn: 3,
MaxContractAgeInBlocks: 100,
PricePerTbPerMonth: 8,
CollateralPrice: 2,
CheckWindow: 10,
})

testMerkle := tree.Root()
var chunk int64 = 10
index := chunk * chunkSize
chunkData := replicaFileData[index : index+chunkSize]

h := sha256.New()
_, err = h.Write([]byte(fmt.Sprintf("%d%x", chunk, chunkData)))
suite.Require().NoError(err)

c := h.Sum(nil)

proof, err := tree.GenerateProof(c, 0)
suite.Require().NoError(err)
jproof, err := json.Marshal(*proof)
suite.Require().NoError(err)

bid, err := sdk.ParseCoinNormalized("500ujkl")
suite.Require().NoError(err)

requestChunkMessage := types.MsgRequestChunk{
Creator: testAccount,
Merkle: testMerkle,
Chunk: chunk,
Bid: bid,
}

_, err = msgSrvr.RequestChunk(ctx, &requestChunkMessage)
suite.Require().NoError(err)

_, found := k.GetOracleRequest(suite.ctx, testAccount, testMerkle, chunk)
suite.Require().True(found)

fulfillMessage := types.MsgFulfillRequest{
Creator: testOracleAccount,
Requester: testAccount,
Merkle: testMerkle,
Chunk: chunk,
Data: chunkData,
HashList: jproof,
}

_, err = msgSrvr.FulfillRequest(ctx, &fulfillMessage)
suite.Require().NoError(err)

_, found = k.GetOracleRequest(suite.ctx, testAccount, testMerkle, chunk)
suite.Require().False(found)

_, found = k.GetOracleEntry(suite.ctx, testAccount, testMerkle, chunk)
suite.Require().True(found)

all := k.GetAllOracleEntries(suite.ctx)
for _, entry := range all {
suite.T().Log(entry.String())
}

tAcc, err := sdk.AccAddressFromBech32(testAccount)
suite.Require().NoError(err)

oAcc, err := sdk.AccAddressFromBech32(testOracleAccount)
suite.Require().NoError(err)

bal := suite.bankKeeper.GetBalance(suite.ctx, oAcc, "ujkl")
suite.Require().Equal(int64(500), bal.Amount.Int64())

bal = suite.bankKeeper.GetBalance(suite.ctx, tAcc, "ujkl")
suite.Require().Equal(int64(100000000000-500), bal.Amount.Int64())

suite.reset()
}
Loading
Loading