diff --git a/README.md b/README.md index debce08f..4247b8ba 100755 --- a/README.md +++ b/README.md @@ -31,10 +31,10 @@ oraid start --rpc.laddr tcp://0.0.0.0:26657 --log_level error oraivisor start --rpc.laddr tcp://0.0.0.0:26657 --log_level error # start websocket subscribe for processing event log in another terminal -oraid tx websocket subscribe --max-try 10 --from $USER --gas="auto" --gas-adjustment="1.2" --chain-id=Oraichain -y +oraid tx websocket subscribe --max-try 10 --from $USER --gas="auto" --gas-adjustment="1.5" --chain-id=Oraichain -y # run as a background process -docker-compose exec -d orai ash -c "echo $KEYRING_PASS | oraid tx websocket subscribe --max-try 10 --from $USER --gas="auto" --gas-adjustment="1.2" --chain-id=Oraichain -y" +docker-compose exec -d orai ash -c "echo $KEYRING_PASS | oraid tx websocket subscribe --max-try 10 --from $USER --gas="auto" --gas-adjustment="1.5" --chain-id=Oraichain -y" ``` ## Build smart contract and interact with it @@ -73,7 +73,7 @@ oraid query wasm contract-state smart orai16at0lzgx3slnqlgvcc7r79056f5wkuczenn09 Run websocket as background process ```bash -echo | oraid tx websocket subscribe --max-try 10 --from $USER --gas="auto" --gas-adjustment="1.2" --chain-id=Oraichain -y +echo | oraid tx websocket subscribe --max-try 10 --from $USER --gas="auto" --gas-adjustment="1.5" --chain-id=Oraichain -y ``` Init smart contracts and create an AI request. To run the script, your current dir must contain the smart-contracts/ directory that already have wasm files built. The directory name with the wasm file should also be consistent. Eg: dir name: classification, then the wasm file is classification.wasm @@ -85,7 +85,7 @@ Init smart contracts and create an AI request. To run the script, your current d Eg: ./scripts/deploy_ai_services.sh classification,cv009 classification_testcase classification_oscript '' '' '{"ai_data_source":["classification","cv009"],"testcase":["classification_testcase"]}' 1 /workspace/oraiwasm 123456789 # open another terminal and run -oraid tx airequest set-aireq oscript_eth "5" "6" 30000orai 1 --from $USER --chain-id Oraichain -y +oraid tx airequest set-aireq oscript_price "5" "6" 30000orai 1 --from $USER --chain-id Oraichain -y # interact with the AI services oraid tx airequest set-aireq classification_oscript '{"image":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSfx__RoRYzLDgXDiJxYGxLihJC4zoqV3V0xg&usqp=CAU","model":"inception_v3","name":"test_image"}' "6" 30000orai 1 --from $USER --chain-id Oraichain -y diff --git a/x/websocket/subscribe/gas_prices.go b/x/websocket/subscribe/gas_prices.go new file mode 100644 index 00000000..62ae3f5c --- /dev/null +++ b/x/websocket/subscribe/gas_prices.go @@ -0,0 +1,30 @@ +package subscribe + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +// MinGasPrices is the struct for querying the minimum gas prices of a node +type MinGasPrices struct { + Price string `json:"minFees"` +} + +func getMinGasPrices() (string, error) { + resp, err := http.Get("http://localhost:1317/provider/minfees?OracleScriptName=min_gas_prices&ValNum=0") + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + var minGasPrices MinGasPrices + // collect response from the request + json.Unmarshal(body, &minGasPrices) + fmt.Println("min gas prices: ", minGasPrices.Price) + return minGasPrices.Price, nil +} diff --git a/x/websocket/subscribe/subscriber_airequest.go b/x/websocket/subscribe/subscriber_airequest.go index f0efe506..665955c1 100755 --- a/x/websocket/subscribe/subscriber_airequest.go +++ b/x/websocket/subscribe/subscriber_airequest.go @@ -27,8 +27,26 @@ func (subscriber *Subscriber) submitReport(msgReport *types.MsgCreateReport) (er subscriber.log.Error(":exploding_head: Failed to validate basic with error: %s", err.Error()) return err } - + // collect gas prices to create a new report + minGasPrices := subscriber.config.Txf.GasPrices().String() + // if the validator does not specify gas prices then we collect from the api get min gas prices + if minGasPrices == "" { + minGasPrices, err = getMinGasPrices() + if err != nil { + subscriber.log.Error(":exploding_head: Failed to collect the minimum gas prices of your node to create a new report with error: %s", err.Error()) + return err + } + // test parsing the gas prices to prevent panic + _, err = sdk.ParseDecCoins(minGasPrices) + if err != nil { + subscriber.log.Error(":exploding_head: Invalid syntax for the minimum gas prices. Expected orai, got error: %s", err.Error()) + return err + } + } txf := subscriber.newTxFactory("websocket") + // add gas prices to pay for the report + txf = txf.WithGasPrices(minGasPrices) + txf = txf.WithGasAdjustment(subscriber.config.Txf.GasAdjustment()) for try := uint64(1); try <= subscriber.config.MaxTry; try++ { subscriber.log.Info(":e-mail: Try to broadcast report transaction(%d/%d)", try, subscriber.config.MaxTry) err = tx.BroadcastTx(*subscriber.cliCtx, txf, msgReport) diff --git a/x/websocket/types/tx_report.go b/x/websocket/types/tx_report.go index 18464379..f2bc1023 100755 --- a/x/websocket/types/tx_report.go +++ b/x/websocket/types/tx_report.go @@ -7,6 +7,11 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) +var ( + reporterNameLen = 20000 // 20KB + msgLen = 200000 // 200KB +) + // regex allow only alphabet, numeric and underscore characters var isStringAlphabetic = regexp.MustCompile(`^[a-zA-Z0-9_]*$`).MatchString @@ -19,7 +24,7 @@ func (msg *MsgCreateReport) Type() string { return "create_report" } // ValidateBasic runs stateless checks on the message func (msg *MsgCreateReport) ValidateBasic() error { reporter := msg.GetReporter() - if reporter.GetAddress().Empty() || len(reporter.GetName()) == 0 || !isStringAlphabetic(reporter.GetName()) { + if reporter.GetAddress().Empty() || len(reporter.GetName()) == 0 || !isStringAlphabetic(reporter.GetName()) || len(reporter.GetName()) >= reporterNameLen { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, reporter.String()) } else if len(msg.GetRequestID()) == 0 || reporter.Validator.Empty() { return sdkerrors.Wrap(ErrMsgReportInvalid, "Request ID / validator address cannot be empty") @@ -28,6 +33,23 @@ func (msg *MsgCreateReport) ValidateBasic() error { } else if msg.GetResultStatus() != ResultSuccess && msg.GetResultStatus() != ResultFailure { return sdkerrors.Wrap(ErrMsgReportInvalid, "result status of the report is not valid") } else { + var dsResultSize int + for _, dsResult := range msg.DataSourceResults { + dsResultSize += len(dsResult.Result) + } + var tcResultSize int + for _, tcResult := range msg.TestCaseResults { + for _, dsResult := range tcResult.DataSourceResults { + tcResultSize += len(dsResult.Result) + } + } + aggregatedResultSize := len(msg.AggregatedResult) + requestIdSize := len(msg.RequestID) + finalLen := dsResultSize + tcResultSize + aggregatedResultSize + requestIdSize + if finalLen >= msgLen { + return sdkerrors.Wrap(ErrMsgReportInvalid, "Size of the report should not be larger than 200KB") + } + _, err := sdk.ParseCoinsNormalized(msg.Fees.String()) if err != nil { return sdkerrors.Wrap(ErrReportFeeTypeInvalid, err.Error())