diff --git a/common/api.go b/common/api.go index 86775b5..4f657dc 100644 --- a/common/api.go +++ b/common/api.go @@ -19,6 +19,7 @@ import ( tmjson "github.com/cometbft/cometbft/libs/json" tmTypes "github.com/cometbft/cometbft/rpc/core/types" tmJsonRPCTypes "github.com/cometbft/cometbft/rpc/jsonrpc/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" ) @@ -125,6 +126,62 @@ func GetAccountBalances(gwCosmosmux *runtime.ServeMux, r *http.Request, bech32ad return result.Balances } +type DappSession struct { + Leader string `protobuf:"bytes,1,opt,name=leader,proto3" json:"leader,omitempty"` + Start string `protobuf:"varint,2,opt,name=start,proto3" json:"start,omitempty"` + StatusHash string `protobuf:"bytes,3,opt,name=status_hash,json=statusHash,proto3" json:"statusHash,omitempty"` + Status string `protobuf:"varint,4,opt,name=status,proto3,enum=kira.layer2.SessionStatus" json:"status,omitempty"` + Gateway string `protobuf:"bytes,5,opt,name=gateway,proto3" json:"gateway,omitempty"` + OnchainMessages []*codectypes.Any `protobuf:"bytes,6,rep,name=onchain_messages,json=onchainMessages,proto3" json:"onchainMessages,omitempty"` +} + +type ExecutionRegistrar struct { + DappName string `protobuf:"bytes,1,opt,name=dapp_name,json=dappName,proto3" json:"dappName,omitempty"` + PrevSession *DappSession `protobuf:"bytes,2,opt,name=prev_session,json=prevSession,proto3" json:"prevSession,omitempty"` + CurrSession *DappSession `protobuf:"bytes,3,opt,name=curr_session,json=currSession,proto3" json:"currSession,omitempty"` + NextSession *DappSession `protobuf:"bytes,4,opt,name=next_session,json=nextSession,proto3" json:"nextSession,omitempty"` +} + +type DappOperator struct { + DappName string `protobuf:"bytes,1,opt,name=dapp_name,json=dappName,proto3" json:"dappName,omitempty"` + Operator string `protobuf:"bytes,2,opt,name=operator,proto3" json:"operator,omitempty"` + Executor bool `protobuf:"varint,3,opt,name=executor,proto3" json:"executor,omitempty"` + Verifier bool `protobuf:"varint,4,opt,name=verifier,proto3" json:"verifier,omitempty"` + Interx string `protobuf:"bytes,5,opt,name=interx,proto3" json:"interx,omitempty"` + Status string `protobuf:"varint,6,opt,name=status,proto3,enum=kira.layer2.OperatorStatus" json:"status,omitempty"` + Rank string `protobuf:"varint,7,opt,name=rank,proto3" json:"rank,omitempty"` + Streak string `protobuf:"varint,8,opt,name=streak,proto3" json:"streak,omitempty"` + Mischance string `protobuf:"varint,9,opt,name=mischance,proto3" json:"mischance,omitempty"` + VerifiedSessions string `protobuf:"varint,10,opt,name=verified_sessions,json=verifiedSessions,proto3" json:"verifiedSessions,omitempty"` + MissedSessions string `protobuf:"varint,11,opt,name=missed_sessions,json=missedSessions,proto3" json:"missedSessions,omitempty"` + BondedLpAmount string `protobuf:"bytes,12,opt,name=bonded_lp_amount,json=bondedLpAmount,proto3" json:"bondedLpAmount"` +} + +type QueryExecutionRegistrarResponse struct { + Dapp interface{} `protobuf:"bytes,1,opt,name=dapp,proto3" json:"dapp,omitempty"` + ExecutionRegistrar *ExecutionRegistrar `json:"executionRegistrar,omitempty"` + Operators []DappOperator `protobuf:"bytes,3,rep,name=operators,proto3" json:"operators"` +} + +func GetExecutionRegistrar(gwCosmosmux *runtime.ServeMux, r *http.Request, appName string) QueryExecutionRegistrarResponse { + r.URL.Path = fmt.Sprintf("/kira/layer2/execution_registrar/%s", appName) + r.URL.RawQuery = "" + r.Method = "GET" + + recorder := httptest.NewRecorder() + gwCosmosmux.ServeHTTP(recorder, r) + resp := recorder.Result() + + result := QueryExecutionRegistrarResponse{} + + err := json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + GetLogger().Error("[grpc-call] Unable to decode response: ", err) + } + + return result +} + // GetAccountNumberSequence is a function to get AccountNumber and Sequence func GetAccountNumberSequence(gwCosmosmux *runtime.ServeMux, r *http.Request, bech32addr string) (uint64, uint64) { _, err := sdk.AccAddressFromBech32(bech32addr) @@ -163,6 +220,44 @@ func GetAccountNumberSequence(gwCosmosmux *runtime.ServeMux, r *http.Request, be return uint64(accountNumber), uint64(sequence) } +func BroadcastTransactionSync(rpcAddr string, txBytes []byte) (string, error) { + endpoint := fmt.Sprintf("%s/broadcast_tx_sync?tx=0x%X", rpcAddr, txBytes) + GetLogger().Info("[rpc-call] Entering rpc call: ", endpoint) + + resp, err := http.Get(endpoint) + if err != nil { + GetLogger().Error("[rpc-call] Unable to connect to ", endpoint) + return "", err + } + defer resp.Body.Close() + + type RPCTempResponse struct { + Jsonrpc string `json:"jsonrpc"` + ID int `json:"id"` + Result struct { + Height string `json:"height"` + Hash string `json:"hash"` + } `json:"result,omitempty"` + Error struct { + Message string `json:"message"` + } `json:"error,omitempty"` + } + + result := new(RPCTempResponse) + err = json.NewDecoder(resp.Body).Decode(result) + if err != nil { + GetLogger().Error("[rpc-call] Unable to decode response: ", err) + return "", err + } + + if resp.StatusCode != http.StatusOK { + GetLogger().Error("[rpc-call] Unable to broadcast transaction: ", result.Error.Message) + return "", errors.New(result.Error.Message) + } + + return result.Result.Hash, nil +} + // BroadcastTransaction is a function to post transaction, returns txHash func BroadcastTransaction(rpcAddr string, txBytes []byte) (string, error) { endpoint := fmt.Sprintf("%s/broadcast_tx_async?tx=0x%X", rpcAddr, txBytes) @@ -418,6 +513,31 @@ func GetKiraStatus(rpcAddr string) *types.KiraStatus { return nil } +// TODO: get abr with appname param +func GetABR(rpcAddr string, appName string) (uint64, error) { + success, _, _ := MakeTendermintRPCRequest(rpcAddr, "/layer2/abr/"+appName, "") + + if success != nil { + result := types.AppBridgeRegistrar{} + + byteData, err := json.Marshal(success) + if err != nil { + GetLogger().Error("[kira-abr] Invalid response format", err) + return 0, err + } + + err = json.Unmarshal(byteData, &result) + if err != nil { + GetLogger().Error("[kira-abr] Invalid response format", err) + return 0, err + } + + return result.Abr, nil + } + + return 0, errors.New("[kira-abr] not found abr") +} + func GetInterxStatus(interxAddr string) *types.InterxStatus { success, _, _ := MakeGetRequest(interxAddr, "/api/status", "") diff --git a/common/main.go b/common/main.go index 492e238..23c75c6 100644 --- a/common/main.go +++ b/common/main.go @@ -78,3 +78,5 @@ func IsCacheExpired(result types.InterxResponse) bool { return isBlockExpire || isTimestampExpire } + +var Layer2Status map[string]string diff --git a/config/constants.go b/config/constants.go index 96d5828..b1f00f3 100755 --- a/config/constants.go +++ b/config/constants.go @@ -2,7 +2,7 @@ package config const ( InterxVersion = "v0.4.48" - SekaiVersion = "v0.3.42" + SekaiVersion = "v0.4.0" CosmosVersion = "v0.47.6" QueryDashboard = "/api/dashboard" @@ -76,7 +76,10 @@ const ( QueryAddrBook = "/api/addrbook" QueryNetInfo = "/api/net_info" + QueryLayer2Status = "/api/layer2/{appName}/status" + Download = "/download" + AppDownload = "/app/download" DataReferenceRegistry = "DRR" DefaultInterxPort = "11000" @@ -182,3 +185,4 @@ var MsgTypes = map[string]string{ } var SupportedEVMChains = [1]string{"goerli"} var SupportedBitcoinChains = [1]string{"testnet"} +var SupportedLayer2Apps = [1]string{"l2chess"} diff --git a/config/init.go b/config/init.go index c0265ff..bfbc12d 100644 --- a/config/init.go +++ b/config/init.go @@ -114,14 +114,10 @@ func defaultConfig() InterxConfigFromFile { configFromFile.RPC = "http://0.0.0.0:26657" configFromFile.PORT = "11000" - configFromFile.Node.NodeType = "seed" - configFromFile.Node.SentryNodeID = "" - configFromFile.Node.SnapshotNodeID = "" - configFromFile.Node.ValidatorNodeID = "" - configFromFile.Node.SeedNodeID = "" + configFromFile.NodeType = "validator" entropy, _ := bip39.NewEntropy(256) - configFromFile.MnemonicFile, _ = bip39.NewMnemonic(entropy) + configFromFile.Mnemonic, _ = bip39.NewMnemonic(entropy) configFromFile.AddrBooks = "addrbook.json" configFromFile.NodeKey = "node_key.json" @@ -140,7 +136,7 @@ func defaultConfig() InterxConfigFromFile { configFromFile.Cache.CachingDuration = 5 configFromFile.Cache.DownloadFileSizeLimitation = "10MB" - configFromFile.Faucet.MnemonicFile = configFromFile.MnemonicFile + configFromFile.Faucet.Mnemonic = configFromFile.Mnemonic configFromFile.Faucet.FaucetAmounts = make(map[string]string) configFromFile.Faucet.FaucetAmounts["stake"] = "100000" @@ -156,6 +152,13 @@ func defaultConfig() InterxConfigFromFile { configFromFile.Faucet.FeeAmounts["ukex"] = "1000ukex" configFromFile.Faucet.TimeLimit = 20 + configFromFile.AppSetting.AppMock = false + configFromFile.AppSetting.AppMode = 0 + configFromFile.AppSetting.AppName = "app_name" + entropy, _ = bip39.NewEntropy(256) + mnemonicFile, _ := bip39.NewMnemonic(entropy) + configFromFile.AppSetting.AppMnemonic = mnemonicFile + configFromFile.Evm = make(map[string]EVMConfig) for _, item := range SupportedEVMChains { evmConfig := EVMConfig{} @@ -197,6 +200,17 @@ func defaultConfig() InterxConfigFromFile { configFromFile.Bitcoin[item] = bitcoinConfig } + configFromFile.CachingBin = false + + configFromFile.Layer2 = make(map[string]Layer2Config) + for _, item := range SupportedLayer2Apps { + layer2Config := Layer2Config{ + RPC: "http://127.0.0.1:9000", + Fee: "300ukex", + } + configFromFile.Layer2[item] = layer2Config + } + return configFromFile } @@ -207,10 +221,6 @@ func InitConfig( grpc string, rpc string, nodeType string, - sentryNodeId string, - snapshotNodeId string, - validatorNodeId string, - seedNodeId string, port string, signingMnemonic string, syncStatus int64, @@ -232,6 +242,10 @@ func InitConfig( nodeDiscoveryTimeout string, nodeKey string, snapshotInterval uint64, + appMode int, + appMock bool, + appName string, + appMnemonic string, ) { configFromFile := defaultConfig() @@ -240,13 +254,9 @@ func InitConfig( configFromFile.RPC = rpc configFromFile.PORT = port - configFromFile.Node.NodeType = nodeType - configFromFile.Node.SentryNodeID = sentryNodeId - configFromFile.Node.SnapshotNodeID = snapshotNodeId - configFromFile.Node.ValidatorNodeID = validatorNodeId - configFromFile.Node.SeedNodeID = seedNodeId + configFromFile.NodeType = nodeType - configFromFile.MnemonicFile = signingMnemonic + configFromFile.Mnemonic = signingMnemonic configFromFile.AddrBooks = addrBooks configFromFile.NodeKey = nodeKey @@ -265,9 +275,14 @@ func InitConfig( configFromFile.Cache.CachingDuration = cachingDuration configFromFile.Cache.DownloadFileSizeLimitation = maxDownloadSize - configFromFile.Faucet.MnemonicFile = faucetMnemonic + configFromFile.Faucet.Mnemonic = faucetMnemonic configFromFile.Faucet.TimeLimit = faucetTimeLimit + configFromFile.AppSetting.AppMock = appMock + configFromFile.AppSetting.AppMode = appMode + configFromFile.AppSetting.AppName = appName + configFromFile.AppSetting.AppMnemonic = appMnemonic + configFromFile.Faucet.FaucetAmounts = make(map[string]string) for _, amount := range strings.Split(faucetAmounts, ",") { coin, err := sdk.ParseCoinNormalized(amount) diff --git a/config/load.go b/config/load.go index 9e73b77..c6e8e5f 100644 --- a/config/load.go +++ b/config/load.go @@ -80,6 +80,20 @@ func serveGRPC(r *http.Request, gwCosmosmux *runtime.ServeMux) (interface{}, int return nil, nil, resp.StatusCode } +func ConvMnemonic2PrivKey(mnemonic string) secp256k1.PrivKey { + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, "") + if err != nil { + panic(err) + } + master, ch := hd.ComputeMastersFromSeed(seed) + priv, err := hd.DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") + if err != nil { + panic(err) + } + + return secp256k1.PrivKey{Key: priv} +} + // LoadAddressAndDenom is a function to load addresses and migrate config using custom bech32 and denom prefixes func LoadAddressAndDenom(configFilePath string, gwCosmosmux *runtime.ServeMux, rpcAddr string, gatewayAddr string) { request, _ := http.NewRequest("GET", "http://"+gatewayAddr+"/kira/gov/custom_prefixes", nil) @@ -125,23 +139,15 @@ func LoadAddressAndDenom(configFilePath string, gwCosmosmux *runtime.ServeMux, r } //=============== interx address =============== - Config.Mnemonic = LoadMnemonic(configFromFile.MnemonicFile) + Config.Mnemonic = LoadMnemonic(configFromFile.Mnemonic) if !bip39.IsMnemonicValid(Config.Mnemonic) { fmt.Println("Invalid Interx Mnemonic: ", Config.Mnemonic) panic("Invalid Interx Mnemonic") } - seed, err := bip39.NewSeedWithErrorChecking(Config.Mnemonic, "") - if err != nil { - panic(err) - } - master, ch := hd.ComputeMastersFromSeed(seed) - priv, err := hd.DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") - if err != nil { - panic(err) - } + privKey1 := ConvMnemonic2PrivKey(Config.Mnemonic) - Config.PrivKey = &secp256k1.PrivKey{Key: priv} + Config.PrivKey = &privKey1 Config.PubKey = Config.PrivKey.PubKey() Config.Address = sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), Config.PubKey.Address()) @@ -175,7 +181,7 @@ func LoadAddressAndDenom(configFilePath string, gwCosmosmux *runtime.ServeMux, r // Faucet Configuration Config.Faucet = FaucetConfig{ - Mnemonic: LoadMnemonic(configFromFile.Faucet.MnemonicFile), + Mnemonic: LoadMnemonic(configFromFile.Faucet.Mnemonic), FaucetAmounts: configFromFile.Faucet.FaucetAmounts, FaucetMinimumAmounts: configFromFile.Faucet.FaucetMinimumAmounts, FeeAmounts: configFromFile.Faucet.FeeAmounts, @@ -187,17 +193,9 @@ func LoadAddressAndDenom(configFilePath string, gwCosmosmux *runtime.ServeMux, r panic("Invalid Faucet Mnemonic") } - seed, err = bip39.NewSeedWithErrorChecking(Config.Faucet.Mnemonic, "") - if err != nil { - panic(err) - } - master, ch = hd.ComputeMastersFromSeed(seed) - priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") - if err != nil { - panic(err) - } + privKey2 := ConvMnemonic2PrivKey(Config.Faucet.Mnemonic) - Config.Faucet.PrivKey = &secp256k1.PrivKey{Key: priv} + Config.Faucet.PrivKey = &privKey2 Config.Faucet.PubKey = Config.Faucet.PrivKey.PubKey() Config.Faucet.Address = sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), Config.Faucet.PubKey.Address()) @@ -223,10 +221,7 @@ func LoadAddressAndDenom(configFilePath string, gwCosmosmux *runtime.ServeMux, r } } -// LoadConfig is a function to load interx configurations from a given file -func LoadConfig(configFilePath string) { - Config = InterxConfig{} - +func LoadConfigFromFile(configFilePath string) InterxConfigFromFile { file, err := ioutil.ReadFile(configFilePath) if err != nil { fmt.Println("Invalid configuration: {}", err) @@ -241,6 +236,15 @@ func LoadConfig(configFilePath string) { panic(err) } + return configFromFile +} + +// LoadConfig is a function to load interx configurations from a given file +func LoadConfig(configFilePath string) { + Config = InterxConfig{} + + configFromFile := LoadConfigFromFile(configFilePath) + // Interx Main Configuration Config.InterxVersion = InterxVersion Config.ServeHTTPS = configFromFile.ServeHTTPS @@ -248,13 +252,14 @@ func LoadConfig(configFilePath string) { Config.RPC = configFromFile.RPC Config.PORT = configFromFile.PORT - Config.Node = configFromFile.Node + Config.NodeType = configFromFile.NodeType fmt.Println("Interx Version: ", Config.InterxVersion) fmt.Println("Interx GRPC: ", Config.GRPC) fmt.Println("Interx RPC : ", Config.RPC) fmt.Println("Interx PORT: ", Config.PORT) + var err error Config.AddrBooks = strings.Split(configFromFile.AddrBooks, ",") Config.NodeKey, err = p2p.LoadOrGenNodeKey(configFromFile.NodeKey) if err != nil { @@ -328,8 +333,10 @@ func LoadConfig(configFilePath string) { Config.Evm = configFromFile.Evm Config.Bitcoin = configFromFile.Bitcoin + Config.Layer2 = configFromFile.Layer2 Config.SnapshotInterval = configFromFile.SnapshotInterval + Config.CachingBin = configFromFile.CachingBin } // GenPrivKey is a function to generate a privKey @@ -342,6 +349,11 @@ func GetReferenceCacheDir() string { return Config.Cache.CacheDir + "/reference" } +// GetReferenceCacheDir is a function to get reference directory +func GetBinCacheDir() string { + return Config.Cache.CacheDir + "/bin" +} + // GetResponseCacheDir is a function to get reference directory func GetResponseCacheDir() string { return Config.Cache.CacheDir + "/response" diff --git a/config/type.go b/config/type.go index a7b8520..63250fa 100644 --- a/config/type.go +++ b/config/type.go @@ -1,11 +1,15 @@ package config import ( - "github.com/KiraCore/interx/types" "github.com/cometbft/cometbft/p2p" crypto "github.com/cosmos/cosmos-sdk/crypto/types" ) +const ( + Executor int = iota + Verifier +) + // FaucetConfig is a struct to be used for Faucet configuration type FaucetConfig struct { Mnemonic string `json:"mnemonic"` @@ -87,6 +91,18 @@ type BitcoinConfig struct { BTC_FAUCET string `json:"btc_faucet"` } +type Layer2Config struct { + RPC string `json:"rpc"` + Fee string `json:"fee"` +} + +type AppSettingConfig struct { + AppMode int `json:"app_mode"` + AppMock bool `json:"app_mock"` + AppName string `json:"app_name"` + AppMnemonic string `json:"app_mnemonic"` +} + // InterxConfig is a struct to be used for interx configuration type InterxConfig struct { InterxVersion string `json:"interx_version"` @@ -95,7 +111,7 @@ type InterxConfig struct { GRPC string `json:"grpc"` RPC string `json:"rpc"` PORT string `json:"port"` - Node types.NodeConfig `json:"node"` + NodeType string `json:"node_type"` Mnemonic string `json:"mnemonic"` AddrBooks []string `json:"addrbooks"` NodeKey *p2p.NodeKey `json:"node_key"` @@ -110,7 +126,10 @@ type InterxConfig struct { RPCMethods RPCConfig `json:"rpc_methods"` Evm map[string]EVMConfig `json:"evm"` Bitcoin map[string]BitcoinConfig `json:"bitcoin"` + Layer2 map[string]Layer2Config `json:"layer2"` SnapshotInterval uint64 `json:"snapshot_interval"` + AppSetting AppSettingConfig `json:"app_setting"` + CachingBin bool `json:"caching_bin"` } // InterxConfigFromFile is a struct to be used for interx configuration file @@ -119,8 +138,8 @@ type InterxConfigFromFile struct { GRPC string `json:"grpc"` RPC string `json:"rpc"` PORT string `json:"port"` - Node types.NodeConfig `json:"node"` - MnemonicFile string `json:"mnemonic"` + NodeType string `json:"node_type"` // sentry, proxy, seed, validator + Mnemonic string `json:"mnemonic"` AddrBooks string `json:"addrbooks"` NodeKey string `json:"node_key"` TxModes string `json:"tx_modes"` @@ -133,7 +152,7 @@ type InterxConfigFromFile struct { DownloadFileSizeLimitation string `json:"download_file_size_limitation"` } `json:"cache"` Faucet struct { - MnemonicFile string `json:"mnemonic"` + Mnemonic string `json:"mnemonic"` FaucetAmounts map[string]string `json:"faucet_amounts"` FaucetMinimumAmounts map[string]string `json:"faucet_minimum_amounts"` FeeAmounts map[string]string `json:"fee_amounts"` @@ -141,5 +160,8 @@ type InterxConfigFromFile struct { } `json:"faucet"` Evm map[string]EVMConfig `json:"evm"` Bitcoin map[string]BitcoinConfig `json:"bitcoin"` + Layer2 map[string]Layer2Config `json:"layer2"` SnapshotInterval uint64 `json:"snapshot_interval"` + AppSetting AppSettingConfig `json:"app_setting"` + CachingBin bool `json:"caching_bin"` } diff --git a/database/layer2.go b/database/layer2.go new file mode 100644 index 0000000..8345642 --- /dev/null +++ b/database/layer2.go @@ -0,0 +1,81 @@ +package database + +import ( + "github.com/KiraCore/interx/config" + "github.com/sonyarouje/simdb/db" +) + +// Layer2Data is a struct for layer2 details. +type Layer2Data struct { + Id string `json:"id"` + Data string `json:"data"` +} + +// ID is a field for facuet claim struct. +func (c Layer2Data) ID() (jsonField string, value interface{}) { + value = c.Id + jsonField = "id" + return +} + +func LoadLayer2DbDriver() { + DisableStdout() + driver, _ := db.New(config.GetDbCacheDir() + "/layer2") + EnableStdout() + + layer2Db = driver +} + +// GetLayer2State is a function to get layer2 app state stored +func GetLayer2State(id string) (string, error) { + if layer2Db == nil { + panic("cache dir not set") + } + + data := Layer2Data{} + + DisableStdout() + err := layer2Db.Open(Layer2Data{}).Where("id", "=", id).First().AsEntity(&data) + EnableStdout() + + if err != nil { + return "", err + } + + return data.Data, nil +} + +// GetAllLayer2s is a function to get all layer2Times +func GetAllLayer2s() []interface{} { + if layer2Db == nil { + panic("cache dir not set") + } + + DisableStdout() + rawData := layer2Db.Open(Layer2Data{}).RawArray() + EnableStdout() + + return rawData +} + +// SetLayer2State is a function to set layer2 app status +func SetLayer2State(id string, data string) { + if layer2Db == nil { + panic("cache dir not set") + } + + DisableStdout() + err := layer2Db.Open(Layer2Data{}).Insert(Layer2Data{ + Id: id, + Data: data, + }) + EnableStdout() + + if err != nil { + panic(err) + } +} + +var ( + layer2Db *db.Driver +) diff --git a/functions/interx.go b/functions/interx.go index 9368b42..e82d68c 100644 --- a/functions/interx.go +++ b/functions/interx.go @@ -622,6 +622,20 @@ func RegisterInterxFunctions() { }`, ) + AddInterxFunction( + "AppDownload", + config.AppDownload, + `{ + "description": "AppDownload is a function to download a application related bin files", + "parameters": { + "app_name": { + "type": "string", + "description": "This represents the app name." + } + } + }`, + ) + AddInterxFunction( "QueryValidators", config.QueryValidators, @@ -1009,6 +1023,11 @@ func RegisterInterxFunctions() { "type": "bool", "description": "This is an option to query only connected ips.", "optional": true + }, + "app": { + "type": "string", + "description": "This is an option to query only nodes by app_name or app_id.", + "optional": true } }, "response": { diff --git a/gateway/application_check.go b/gateway/application_check.go new file mode 100644 index 0000000..4a5cb80 --- /dev/null +++ b/gateway/application_check.go @@ -0,0 +1,45 @@ +package gateway + +import ( + "encoding/json" + + "github.com/KiraCore/interx/common" +) + +func ToString(data interface{}) string { + out, err := json.Marshal(data) + if err != nil { + panic(err) + } + + return string(out) +} + +func NodeSyncState(rpcAddr string) bool { + success, _, _ := common.MakeTendermintRPCRequest(rpcAddr, "/status", "") + + if success == nil { + return false + } + type TempResponse struct { + SyncInfo struct { + CatchingUp bool `json:"catching_up"` + } `json:"sync_info"` + } + + result := TempResponse{} + + byteData, err := json.Marshal(success) + if err != nil { + common.GetLogger().Error("[rosetta-query-networkstatus] Invalid response format", err) + return false + } + + err = json.Unmarshal(byteData, &result) + if err != nil { + common.GetLogger().Error("[rosetta-query-networkstatus] Invalid response format", err) + return false + } + + return !result.SyncInfo.CatchingUp +} diff --git a/gateway/interx/download.go b/gateway/interx/download.go index 573c282..6f1c2ac 100644 --- a/gateway/interx/download.go +++ b/gateway/interx/download.go @@ -13,6 +13,7 @@ import ( // RegisterInterxDownloadRoutes registers download routers. func RegisterInterxDownloadRoutes(r *mux.Router, gwCosmosmux *runtime.ServeMux, rpcAddr string) { r.PathPrefix(config.Download).HandlerFunc(DownloadReference()).Methods("GET") + r.PathPrefix(config.AppDownload).HandlerFunc(DownloadAppBin()).Methods("GET") common.AddRPCMethod("GET", config.Download, "This is an API to download files.", true) } @@ -29,3 +30,19 @@ func DownloadReference() http.HandlerFunc { } } } + +// DownloadReference is a function to download reference. +func DownloadAppBin() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // TODO: download bin files + config.Config.CachingBin = true + filename := strings.TrimPrefix(r.URL.Path, config.AppDownload+"/") + + common.GetLogger().Info("[download] Entering reference download: ", filename) + + if len(filename) != 0 { + http.ServeFile(w, r, config.GetBinCacheDir()+"/"+filename) + } + config.Config.CachingBin = false + } +} diff --git a/gateway/interx/nodelist.go b/gateway/interx/nodelist.go index 082bb75..43d8d74 100644 --- a/gateway/interx/nodelist.go +++ b/gateway/interx/nodelist.go @@ -38,6 +38,7 @@ func queryPubP2PNodeList(r *http.Request, rpcAddr string) (interface{}, interfac peers_only := r.FormValue("peers_only") == "true" is_random := r.FormValue("order") == "random" is_format_simple := r.FormValue("format") == "simple" + appInfo := r.FormValue("app") if is_random { dest := make([]types.P2PNode, len(response.NodeList)) @@ -74,6 +75,9 @@ func queryPubP2PNodeList(r *http.Request, rpcAddr string) (interface{}, interfac behind, _ := strconv.Atoi(r.FormValue("behind")) if behind == 0 || (node.BlockDiff <= int64(behind) && node.BlockDiff >= -int64(behind)) { if r.FormValue("unsafe") == "true" || node.Safe { + if appInfo != "" && strconv.FormatUint(node.AppId, 10) != appInfo && node.AppName != appInfo { + continue + } dest = append(dest, node) } } diff --git a/gateway/interx/query.go b/gateway/interx/query.go index 8e53080..1e5e8b4 100644 --- a/gateway/interx/query.go +++ b/gateway/interx/query.go @@ -103,7 +103,7 @@ func queryStatusHandle(rpcAddr string) (interface{}, interface{}, int) { result.InterxInfo.CatchingUp = sentryStatus.SyncInfo.CatchingUp } - result.InterxInfo.Node = config.Config.Node + result.InterxInfo.NodeType = config.Config.NodeType result.InterxInfo.KiraAddr = config.Config.Address result.InterxInfo.KiraPubKey = config.Config.PubKey.String() @@ -114,6 +114,21 @@ func queryStatusHandle(rpcAddr string) (interface{}, interface{}, int) { result.InterxInfo.InterxVersion = config.Config.InterxVersion result.InterxInfo.SekaiVersion = config.Config.SekaiVersion + result.AppInfo.Name = config.Config.AppSetting.AppName + result.AppInfo.Mock = config.Config.AppSetting.AppMock + switch config.Config.AppSetting.AppMode { + case config.Executor: + result.AppInfo.Mode = "executor" + case config.Verifier: + result.AppInfo.Mode = "verifier" + } + // TODO: get abr(application index in Applicaiton Bridge Registrar) + abr, err := common.GetABR(rpcAddr, config.Config.AppSetting.AppName) + if err != nil { + return common.ServeError(0, "", err.Error(), http.StatusInternalServerError) + } + result.AppInfo.Abr = abr + return result, nil, http.StatusOK } diff --git a/gateway/interx/validators_test.go b/gateway/interx/validators_test.go index 3cc0e0d..4a5364a 100644 --- a/gateway/interx/validators_test.go +++ b/gateway/interx/validators_test.go @@ -28,11 +28,11 @@ type ValidatorsTestSuite struct { dumpConsensusQuery tmJsonRPCTypes.RPCResponse } -type slashingServer struct { +type SlashingServer struct { slashingTypes.UnimplementedQueryServer } -type multiStakingServer struct { +type MultiStakingServer struct { multiStakingTypes.UnimplementedQueryServer multiStakingTypes.UnimplementedMsgServer } @@ -44,7 +44,7 @@ type ValidatorsStatus struct { } `json:"pagination,omitempty"` } -func (s *slashingServer) SigningInfos(ctx context.Context, req *slashingTypes.QuerySigningInfosRequest) (*slashingTypes.QuerySigningInfosResponse, error) { +func (s *SlashingServer) SigningInfos(ctx context.Context, req *slashingTypes.QuerySigningInfosRequest) (*slashingTypes.QuerySigningInfosResponse, error) { return &slashingTypes.QuerySigningInfosResponse{Validators: []stakingTypes.QueryValidator{ { Address: "test_address", @@ -52,7 +52,7 @@ func (s *slashingServer) SigningInfos(ctx context.Context, req *slashingTypes.Qu }}, nil } -func (s *multiStakingServer) StakingPools(ctx context.Context, req *multiStakingTypes.QueryStakingPoolsRequest) (*multiStakingTypes.QueryStakingPoolsResponse, error) { +func (s *MultiStakingServer) StakingPools(ctx context.Context, req *multiStakingTypes.QueryStakingPoolsRequest) (*multiStakingTypes.QueryStakingPoolsResponse, error) { return &multiStakingTypes.QueryStakingPoolsResponse{Pools: []multiStakingTypes.StakingPool{ { Id: 1, @@ -150,7 +150,7 @@ func TestValidatorsTestSuite(t *testing.T) { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() - slashingTypes.RegisterQueryServer(s, &slashingServer{}) + slashingTypes.RegisterQueryServer(s, &SlashingServer{}) log.Printf("server listening at %v", lis.Addr()) go func() { diff --git a/gateway/layer2/main.go b/gateway/layer2/main.go new file mode 100644 index 0000000..1019575 --- /dev/null +++ b/gateway/layer2/main.go @@ -0,0 +1,11 @@ +package layer2 + +import ( + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" +) + +// RegisterRequest is a function to register requests. +func RegisterRequest(router *mux.Router, gwCosmosmux *runtime.ServeMux, rpcAddr string) { + RegisterStatusRoutes(router, gwCosmosmux, rpcAddr) +} diff --git a/gateway/layer2/status.go b/gateway/layer2/status.go new file mode 100644 index 0000000..08014d5 --- /dev/null +++ b/gateway/layer2/status.go @@ -0,0 +1,54 @@ +package layer2 + +import ( + "net/http" + + "github.com/KiraCore/interx/common" + "github.com/KiraCore/interx/config" + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" +) + +// RegisterBlockRoutes registers layer2/status query routers. +func RegisterStatusRoutes(r *mux.Router, gwCosmosmux *runtime.ServeMux, rpcAddr string) { + r.HandleFunc(config.QueryLayer2Status, QueryLayer2StatusRequest(gwCosmosmux, rpcAddr)).Methods("GET") + + common.AddRPCMethod("GET", config.QueryLayer2Status, "This is an API to query layer2 status.", true) +} + +func queryLayer2StatusHandle(rpcAddr string, r *http.Request) (interface{}, interface{}, int) { + queries := mux.Vars(r) + appName := queries["appName"] + _ = r.ParseForm() + return common.Layer2Status[appName], nil, http.StatusOK +} + +// QueryLayer2StatusRequest is a function to query layer2 status. +func QueryLayer2StatusRequest(gwCosmosmux *runtime.ServeMux, rpcAddr string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var statusCode int + request := common.GetInterxRequest(r) + response := common.GetResponseFormat(request, rpcAddr) + + common.GetLogger().Info("[query-layer2-status] Entering Layer2 status query") + + if !common.RPCMethods["GET"][config.QueryLayer2Status].Enabled { + response.Response, response.Error, statusCode = common.ServeError(0, "", "API disabled", http.StatusForbidden) + } else { + if common.RPCMethods["GET"][config.QueryLayer2Status].CachingEnabled { + found, cacheResponse, cacheError, cacheStatus := common.SearchCache(request, response) + if found { + response.Response, response.Error, statusCode = cacheResponse, cacheError, cacheStatus + common.WrapResponse(w, request, *response, statusCode, false) + + common.GetLogger().Info("[query-layer2-status] Returning from the cache") + return + } + } + + response.Response, response.Error, statusCode = queryLayer2StatusHandle(rpcAddr, r) + } + + common.WrapResponse(w, request, *response, statusCode, common.RPCMethods["GET"][config.QueryLayer2Status].CachingEnabled) + } +} diff --git a/gateway/main.go b/gateway/main.go index b51f400..bb662dc 100644 --- a/gateway/main.go +++ b/gateway/main.go @@ -6,6 +6,7 @@ import ( "fmt" "mime" "net/http" + "time" "github.com/KiraCore/interx/config" "github.com/KiraCore/interx/database" @@ -14,6 +15,7 @@ import ( cosmosAuth "github.com/KiraCore/interx/proto-gen/cosmos/auth/v1beta1" cosmosBank "github.com/KiraCore/interx/proto-gen/cosmos/bank/v1beta1" kiraGov "github.com/KiraCore/interx/proto-gen/kira/gov" + kiraLayer2 "github.com/KiraCore/interx/proto-gen/kira/layer2" kiraMultiStaking "github.com/KiraCore/interx/proto-gen/kira/multistaking" kiraSlashing "github.com/KiraCore/interx/proto-gen/kira/slashing/v1beta1" kiraSpending "github.com/KiraCore/interx/proto-gen/kira/spending" @@ -122,6 +124,10 @@ func GetGrpcServeMux(grpcAddr string) (*runtime.ServeMux, error) { return nil, fmt.Errorf("failed to register gateway: %w", err) } + err = kiraLayer2.RegisterQueryHandler(context.Background(), gwCosmosmux, conn) + if err != nil { + return nil, fmt.Errorf("failed to register gateway: %w", err) + } return gwCosmosmux, nil } @@ -135,6 +141,7 @@ func Run(configFilePath string, log grpclog.LoggerV2) error { database.LoadBlockNanoDbDriver() database.LoadFaucetDbDriver() database.LoadReferenceDbDriver() + database.LoadLayer2DbDriver() serveHTTPS := config.Config.ServeHTTPS grpcAddr := config.Config.GRPC @@ -168,6 +175,18 @@ func Run(configFilePath string, log grpclog.LoggerV2) error { } config.LoadAddressAndDenom(configFilePath, gwCosmosmux, rpcAddr, gatewayAddr) + + for { + syncState := NodeSyncState(rpcAddr) + if syncState { + break + } + + log.Info("Waiting node to be synced") + + time.Sleep(time.Second) + } + tasks.RunTasks(gwCosmosmux, rpcAddr, gatewayAddr) if serveHTTPS { diff --git a/gateway/register.go b/gateway/register.go index 1c57155..88159fa 100644 --- a/gateway/register.go +++ b/gateway/register.go @@ -6,6 +6,7 @@ import ( "github.com/KiraCore/interx/gateway/evm" "github.com/KiraCore/interx/gateway/interx" "github.com/KiraCore/interx/gateway/kira" + "github.com/KiraCore/interx/gateway/layer2" "github.com/KiraCore/interx/gateway/rosetta" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" @@ -19,4 +20,5 @@ func RegisterRequest(router *mux.Router, gwCosmosmux *runtime.ServeMux, rpcAddr rosetta.RegisterRequest(router, gwCosmosmux, rpcAddr) bitcoin.RegisterRequest(router, rpcAddr) evm.RegisterRequest(router, rpcAddr) + layer2.RegisterRequest(router, gwCosmosmux, rpcAddr) } diff --git a/main.go b/main.go index 9ab2f62..092e604 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,19 @@ package main import ( + "crypto/ecdsa" + "encoding/hex" + "encoding/json" "flag" "fmt" + "log" "os" "github.com/KiraCore/interx/common" "github.com/KiraCore/interx/config" "github.com/KiraCore/interx/gateway" _ "github.com/KiraCore/interx/statik" + "github.com/ethereum/go-ethereum/crypto" "github.com/tyler-smith/go-bip39" "google.golang.org/grpc/grpclog" ) @@ -46,10 +51,6 @@ func main() { initRPCPtr := initCommand.String("rpc", "http://0.0.0.0:26657", "The rpc endpoint of the sekaid.") initNodeType := initCommand.String("node_type", "seed", "The node type.") - initSentryNodeId := initCommand.String("sentry_node_id", "", "The sentry node id.") - initSnapshotNodeId := initCommand.String("snapshot_node_id", "", "The snapshot node id.") - initValidatorNodeId := initCommand.String("validator_node_id", "", "The validator node id.") - initSeedNodeId := initCommand.String("seed_node_id", "", "The seed node id.") initPortPtr := initCommand.String("port", "11000", "The interx port.") @@ -81,6 +82,10 @@ func main() { initNodeDiscoveryTendermintPort := initCommand.String("node_discovery_tendermint_port", "26657", "The default tendermint port to be used in node discovery") initNodeDiscoveryTimeout := initCommand.String("node_discovery_timeout", "3s", "The connection timeout to be used in node discovery") initSnapShotInterval := initCommand.Uint64("snapshot_interval", 100, "The snapshot time interval amount to be used to sleep snapshot function") + initAppMode := initCommand.Int("app_mode", 0, "The application mode to be used to decide interx to assist which type of operator") + initAppMock := initCommand.Bool("app_mock", false, "The application mock state for the purpose of test") + initAppName := initCommand.String("app_name", "kira-executor", "The application name for interx to assist") + initAppMnemonic := initCommand.String("app_mnemonic", "", "The application name for interx to assist") startHomePtr := startCommand.String("home", homeDir+"/.interxd", "The interx configuration path. (Required)") @@ -132,10 +137,6 @@ func main() { *initGrpcPtr, *initRPCPtr, *initNodeType, - *initSentryNodeId, - *initSnapshotNodeId, - *initValidatorNodeId, - *initSeedNodeId, *initPortPtr, *initSigningMnemonicPtr, *initSyncStatus, @@ -157,6 +158,10 @@ func main() { *initNodeDiscoveryTimeout, nodeKey, *initSnapShotInterval, + *initAppMode, + *initAppMock, + *initAppName, + *initAppMnemonic, ) fmt.Printf("Created interx configuration file: %s\n", *initHomePtr+"/config.json") @@ -193,6 +198,65 @@ func main() { fmt.Println(config.InterxVersion) return } + case "list-keys": + configFilePath := *startHomePtr + "/config.json" + configFromFile := config.LoadConfigFromFile(configFilePath) + type PubKeys struct { + Signing string `json:"signing"` + Faucet string `json:"faucet"` + App string `json:"application"` + FaucetEth string `json:"faucet-eth"` + FaucetBTC string `json:"faucet-btc"` + } + + signingMnemonic := config.LoadMnemonic(configFromFile.Mnemonic) + if !bip39.IsMnemonicValid(signingMnemonic) { + fmt.Println("Invalid Interx Mnemonic: ", signingMnemonic) + panic("Invalid Interx Mnemonic") + } + + signingPriv := config.ConvMnemonic2PrivKey(signingMnemonic) + + faucetMnemonic := config.LoadMnemonic(configFromFile.Faucet.Mnemonic) + if !bip39.IsMnemonicValid(faucetMnemonic) { + fmt.Println("Invalid Interx Mnemonic: ", faucetMnemonic) + panic("Invalid Interx Mnemonic") + } + + faucetPriv := config.ConvMnemonic2PrivKey(faucetMnemonic) + + appMnemonic := config.LoadMnemonic(configFromFile.AppSetting.AppMnemonic) + if !bip39.IsMnemonicValid(appMnemonic) { + fmt.Println("Invalid Interx Mnemonic: ", appMnemonic) + panic("Invalid Interx Mnemonic") + } + + appPriv := config.ConvMnemonic2PrivKey(appMnemonic) + + ethPrivKey, err := crypto.HexToECDSA(configFromFile.Evm["goerli"].Faucet.PrivateKey) + if err != nil { + log.Fatal(err) + } + + ethPubKey := ethPrivKey.Public() + ethPubKeyECDSA, ok := ethPubKey.(*ecdsa.PublicKey) + if !ok { + log.Fatal("error casting public key to ECDSA") + } + + ethPubKeyBytes := crypto.FromECDSAPub(ethPubKeyECDSA) + + pubKeys := PubKeys{ + Signing: signingPriv.PubKey().String(), + Faucet: faucetPriv.PubKey().String(), + App: appPriv.PubKey().String(), + FaucetEth: hex.EncodeToString(ethPubKeyBytes), + FaucetBTC: configFromFile.Bitcoin["testnet"].BTC_FAUCET, + } + data, _ := json.Marshal(pubKeys) + + fmt.Println(string(data)) + return default: fmt.Println("init or start command is available.") os.Exit(1) diff --git a/tasks/executionRegistrar.go b/tasks/executionRegistrar.go new file mode 100644 index 0000000..1963492 --- /dev/null +++ b/tasks/executionRegistrar.go @@ -0,0 +1,65 @@ +package tasks + +import ( + "encoding/json" + "errors" + "net/http" + "time" + + "github.com/KiraCore/interx/common" + "github.com/KiraCore/interx/config" + layer2types "github.com/KiraCore/sekai/x/layer2/types" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" +) + +var ( + ExecutionRegistrar layer2types.ExecutionRegistrar +) + +func QueryExecutionRegistrar(gwCosmosmux *runtime.ServeMux, gatewayAddr string, rpcAddr string) error { + result := layer2types.QueryExecutionRegistrarResponse{} + appstateQueryRequest, _ := http.NewRequest("GET", "http://"+gatewayAddr+"/kira/layer2/execution_registrar/"+config.Config.AppSetting.AppName, nil) + + appstateQueryResponse, failure, _ := common.ServeGRPC(appstateQueryRequest, gwCosmosmux) + + if appstateQueryResponse == nil { + return errors.New(ToString(failure)) + } + + byteData, err := json.Marshal(appstateQueryResponse) + if err != nil { + return err + } + + err = json.Unmarshal(byteData, &result) + if err != nil { + return err + } + + ExecutionRegistrar = *result.ExecutionRegistrar + + return nil +} + +func SyncExecutionRegistrar(gwCosmosmux *runtime.ServeMux, gatewayAddr string, rpcAddr string, isLog bool) { + lastBlock := int64(0) + for { + if config.Config.AppSetting.AppMock { + continue + } + + if common.NodeStatus.Block != lastBlock { + err := QueryExecutionRegistrar(gwCosmosmux, gatewayAddr, rpcAddr) + + if err != nil && isLog { + common.GetLogger().Error("[sync-proposals] Failed to query proposals: ", err) + } + + lastBlock = common.NodeStatus.Block + } + + // TODO: check if appdata has interx address that is matched our app_pubkey, interx will support this app + + time.Sleep(1 * time.Second) + } +} diff --git a/tasks/layer2_status.go b/tasks/layer2_status.go new file mode 100644 index 0000000..3c2305f --- /dev/null +++ b/tasks/layer2_status.go @@ -0,0 +1,219 @@ +package tasks + +import ( + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/KiraCore/interx/common" + "github.com/KiraCore/interx/config" + "github.com/KiraCore/interx/database" + layer2types "github.com/KiraCore/sekai/x/layer2/types" + cosmostypes "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" + legacytx "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + + // banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + types "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" +) + +func storeL2Status(gwCosmosmux *runtime.ServeMux, isLog bool) { + for l2AppName, conf := range config.Config.Layer2 { + rpcAddr := conf.RPC + url := fmt.Sprintf("%s/api/v1/status", rpcAddr) + resp, err := http.Get(url) + if err != nil { + common.GetLogger().Error("[layer2-status] Unable to connect to ", url) + return + } + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + common.GetLogger().Error("[layer2-status] Unable to read status export result", err) + return + } + + database.SetLayer2State("chess", string(bodyBytes)) + + if isLog { + common.GetLogger().Info("[layer2-status] (new state): ", time.Now().UTC()) + } + + common.Layer2Status[l2AppName] = string(bodyBytes) + + r, err := http.NewRequest("GET", getInterxRPC(), strings.NewReader("")) + if err != nil { + common.GetLogger().Error("[layer2]: ", err) + return + } + + // If leader of session + registrar := common.GetExecutionRegistrar(gwCosmosmux, r, l2AppName) + + // Workflow + // if current session is empty and next session leader is myself, send MsgExecuteDappTx (this is needed at start phase) + // if current session leader is myself and status is scheduled, send MsgExecuteDappTx (this is needed when session's accepted and next session moved to current session) + // if current session leader is myself and status is on-going, send MsgTransitionDappTx + // if current session looks okay, send MsgApproveDappTransitionTx if not, send MsgRejectDappTransitionTx (check registrar.ExecutionRegistrar.CurrSession.Start) + // check all the state transition flow's working fine + // how often should it execute approve tx? Since it will let sessions to be moved + + if registrar.ExecutionRegistrar != nil && registrar.ExecutionRegistrar.NextSession != nil { + currSession := registrar.ExecutionRegistrar.CurrSession + nextSession := registrar.ExecutionRegistrar.NextSession + for _, operator := range registrar.Operators { + if operator.Interx != config.Config.Address { + continue + } + if currSession == nil && operator.Operator == nextSession.Leader { + msg := &layer2types.MsgExecuteDappTx{ + Sender: config.Config.Address, + DappName: l2AppName, + Gateway: "interx.com", + } + + sendTx(msg, conf.Fee, gwCosmosmux, r) + time.Sleep(time.Second * 5) + } + } + } + + if registrar.ExecutionRegistrar != nil && registrar.ExecutionRegistrar.CurrSession != nil { + currSession := registrar.ExecutionRegistrar.CurrSession + + for _, operator := range registrar.Operators { + if operator.Interx != config.Config.Address { + continue + } + + if currSession.Leader == operator.Operator { + // sekai RPC submit + // msg := &banktypes.MsgSend{ + // FromAddress: config.Config.Address, + // ToAddress: config.Config.Faucet.Address, + // Amount: sdk.Coins{sdk.NewInt64Coin("ukex", 100)}, + // } + msg := &layer2types.MsgTransitionDappTx{ + Sender: config.Config.Address, + DappName: l2AppName, + StatusHash: common.GetSha256SumFromBytes([]byte(common.Layer2Status[l2AppName] + fmt.Sprint(time.Now().Unix()))), + OnchainMessages: []*types.Any{}, + Version: "0cc0", // TODO: to be queried from dapp docker - to be equal to dapp.Bin[0].Hash + } + sendTx(msg, conf.Fee, gwCosmosmux, r) + time.Sleep(time.Second * 5) + } + + now := time.Now().Unix() + start, err := strconv.Atoi(currSession.Start) + if err != nil || start+60 < int(now) { // execute transition once per min for now + msg := &layer2types.MsgApproveDappTransitionTx{ + Sender: config.Config.Address, + DappName: l2AppName, + Version: "0cc0", // TODO: to be queried from dapp docker - to be equal to dapp.Bin[0].Hash + } + // msg := layer2types.MsgRejectDappTransitionTx{ + // Sender: config.Config.Address, + // DappName: l2AppName, + // Version: "0cc0", // TODO: to be queried from dapp docker - to be equal to dapp.Bin[0].Hash + // } + sendTx(msg, conf.Fee, gwCosmosmux, r) + time.Sleep(time.Second * 5) + } + } + } + } +} + +func getInterxRPC() string { + return "http://127.0.0.1:" + config.Config.PORT +} + +// StoreL2Status is a function storing layer2 app status +func StoreL2Status(gwCosmosmux *runtime.ServeMux, isLog bool) { + for { + storeL2Status(gwCosmosmux, isLog) + + if isLog { + common.GetLogger().Info("[node-status] Syncing node status") + common.GetLogger().Info("[node-status] Chain_id = ", common.NodeStatus.Chainid) + common.GetLogger().Info("[node-status] Block = ", common.NodeStatus.Block) + common.GetLogger().Info("[node-status] Blocktime = ", common.NodeStatus.Blocktime) + } + + time.Sleep(time.Duration(config.Config.Block.StatusSync) * time.Second) + } +} + +func sendTx(msg cosmostypes.Msg, feeStr string, gwCosmosmux *runtime.ServeMux, r *http.Request) error { + accountNumber, sequence := common.GetAccountNumberSequence(gwCosmosmux, r.Clone(r.Context()), config.Config.Address) + + feeAmount, err := sdk.ParseCoinNormalized(feeStr) + if err != nil { + return err + } + + msgs := []sdk.Msg{msg} + fee := legacytx.NewStdFee(200000, sdk.NewCoins(feeAmount)) //Fee handling + memo := "layer2-transition" + + // TODO: get it from somewhere + common.NodeStatus.Chainid = "testing" + sigs := make([]legacytx.StdSignature, 1) + signBytes := legacytx.StdSignBytes(common.NodeStatus.Chainid, accountNumber, sequence, 0, fee, msgs, memo, nil) + sig, err := config.Config.PrivKey.Sign(signBytes) + if err != nil { + common.GetLogger().Error("[layer2] Failed to sign transaction: ", err) + panic(err) + } + + sigs[0] = legacytx.StdSignature{PubKey: config.Config.PubKey, Signature: sig} + + stdTx := legacytx.NewStdTx(msgs, fee, sigs, memo) + + txBuilder := config.EncodingCg.TxConfig.NewTxBuilder() + err = txBuilder.SetMsgs(stdTx.GetMsgs()...) + if err != nil { + common.GetLogger().Error("[layer2] Failed to set tx msgs: ", err) + return err + } + + sigV2, err := stdTx.GetSignaturesV2() + if err != nil { + common.GetLogger().Error("[layer2] Failed to get SignatureV2: ", err) + return err + } + + sigV2[0].Sequence = sequence + + err = txBuilder.SetSignatures(sigV2...) + if err != nil { + common.GetLogger().Error("[layer2] Failed to set SignatureV2: ", err) + return err + } + + txBuilder.SetMemo(stdTx.GetMemo()) + txBuilder.SetFeeAmount(stdTx.GetFee()) + txBuilder.SetGasLimit(stdTx.GetGas()) + + txBytes, err := config.EncodingCg.TxConfig.TxEncoder()(txBuilder.GetTx()) + if err != nil { + common.GetLogger().Error("[layer2] Failed to get tx bytes: ", err) + return err + } + + txHash, err := common.BroadcastTransactionSync(config.Config.RPC, txBytes) + if err != nil { + common.GetLogger().Error("[layer2] Failed to broadcast transaction: ", err) + return err + } + + common.GetLogger().Info("txHash: ", txHash) + return nil +} diff --git a/tasks/main.go b/tasks/main.go index 80b5782..0bda7ad 100644 --- a/tasks/main.go +++ b/tasks/main.go @@ -16,4 +16,5 @@ func RunTasks(gwCosmosmux *runtime.ServeMux, rpcAddr string, gatewayAddr string) go SyncProposals(gwCosmosmux, gatewayAddr, rpcAddr, false) go CalcSnapshotChecksum(false) go SyncBitcoinWallets() + go StoreL2Status(gwCosmosmux, false) } diff --git a/tasks/node_discover.go b/tasks/node_discover.go index 3817439..1d99cc3 100644 --- a/tasks/node_discover.go +++ b/tasks/node_discover.go @@ -195,6 +195,52 @@ func QueryKiraStatus(rpcAddr string) (tmTypes.ResultStatus, error) { return result, nil } +func queryInterxStatus(rpcAddr string) (types.InterxStatus, error) { + result := types.InterxStatus{} + + resp, err := http.Get(rpcAddr) + if err != nil { + return types.InterxStatus{}, err + } + defer resp.Body.Close() + + err = json.NewDecoder(resp.Body).Decode(&result) + if err != nil { + return types.InterxStatus{}, err + } + + return result, nil +} + +func QueryInterxStatus(ipAddr string) (types.InterxStatus, error) { + interxState, err := queryInterxStatus("http://" + ipAddr + ":11000") + if err == nil { + return interxState, err + } + + interxState, err = queryInterxStatus("http://" + ipAddr + ":21000") + if err == nil { + return interxState, err + } + + interxState, err = queryInterxStatus("http://" + ipAddr + ":31000") + if err == nil { + return interxState, err + } + + interxState, err = queryInterxStatus("http://" + ipAddr + ":41000") + if err == nil { + return interxState, err + } + + interxState, err = queryInterxStatus("http://" + ipAddr + ":51000") + if err == nil { + return interxState, err + } + + return interxState, err +} + func QueryStatus(ipAddr string) (tmTypes.ResultStatus, error) { result, err := QueryKiraStatus("http://" + ipAddr + ":16657") if err == nil { @@ -573,6 +619,9 @@ func NodeDiscover(rpcAddr string, isLog bool) { interxStatus := common.GetInterxStatus(interxAddress) if interxStatus != nil { + nodeInfo.AppId = interxStatus.AppInfo.Abr + nodeInfo.AppName = interxStatus.AppInfo.Name + interxEndTime := makeTimestamp() interxInfo := types.InterxNode{} @@ -581,7 +630,7 @@ func NodeDiscover(rpcAddr string, isLog bool) { interxInfo.Ping = interxEndTime - interxStartTime interxInfo.Moniker = interxStatus.InterxInfo.Moniker interxInfo.Faucet = interxStatus.InterxInfo.FaucetAddr - interxInfo.Type = interxStatus.InterxInfo.Node.NodeType + interxInfo.Type = interxStatus.InterxInfo.NodeType interxInfo.InterxVersion = interxStatus.InterxInfo.InterxVersion interxInfo.SekaiVersion = interxStatus.InterxInfo.SekaiVersion interxInfo.Alive = true diff --git a/types/interx.go b/types/interx.go index 370c9d1..69bf5c2 100644 --- a/types/interx.go +++ b/types/interx.go @@ -1,13 +1,5 @@ package types -type NodeConfig struct { - NodeType string `json:"node_type"` - SentryNodeID string `json:"sentry_node_id"` - SnapshotNodeID string `json:"snapshot_node_id"` - ValidatorNodeID string `json:"validator_node_id"` - SeedNodeID string `json:"seed_node_id"` -} - type InterxStatus struct { ID string `json:"id"` InterxInfo struct { @@ -15,21 +7,28 @@ type InterxStatus struct { Type string `json:"type"` Value string `json:"value"` } `json:"pub_key,omitempty"` - Moniker string `json:"moniker"` - KiraAddr string `json:"kira_addr"` - KiraPubKey string `json:"kira_pub_key"` - FaucetAddr string `json:"faucet_addr"` - GenesisChecksum string `json:"genesis_checksum"` - ChainID string `json:"chain_id"` - InterxVersion string `json:"version,omitempty"` - SekaiVersion string `json:"sekai_version,omitempty"` - LatestBlockHeight string `json:"latest_block_height"` - CatchingUp bool `json:"catching_up"` - Node NodeConfig `json:"node"` + Moniker string `json:"moniker"` + KiraAddr string `json:"kira_addr"` + AppAddr string `json:"app_addr"` + KiraPubKey string `json:"kira_pub_key"` + FaucetAddr string `json:"faucet_addr"` + GenesisChecksum string `json:"genesis_checksum"` + ChainID string `json:"chain_id"` + InterxVersion string `json:"version,omitempty"` + SekaiVersion string `json:"sekai_version,omitempty"` + LatestBlockHeight string `json:"latest_block_height"` + CatchingUp bool `json:"catching_up"` + NodeType string `json:"node_type"` } `json:"interx_info,omitempty"` NodeInfo NodeInfo `json:"node_info,omitempty"` SyncInfo SyncInfo `json:"sync_info,omitempty"` ValidatorInfo ValidatorInfo `json:"validator_info,omitempty"` + AppInfo struct { + Name string `json:"name"` + Abr uint64 `json:"abr"` + Mode string `json:"mode"` + Mock bool `json:"mock"` + } `json:"app_info"` } type SnapShotChecksumResponse struct { diff --git a/types/nodes.go b/types/nodes.go index 4bec183..bfe7461 100644 --- a/types/nodes.go +++ b/types/nodes.go @@ -39,6 +39,8 @@ type P2PNode struct { Safe bool `json:"safe"` BlockHeightAtSync int64 `json:"block_height_at_sync"` BlockDiff int64 `json:"block_diff"` + AppName string `json:"app_name"` + AppId uint64 `json:"app_id"` PeersNumber int64 `json:"peers_number"` // e.g. 160 CountryCode string `json:"country_code"` // e.g. "DE" DataCenter string `json:"data_center"` // e.g. "Contabo GmbH" diff --git a/types/status.go b/types/status.go index 6e40dde..932e4c4 100644 --- a/types/status.go +++ b/types/status.go @@ -51,3 +51,8 @@ type KiraStatus struct { SyncInfo SyncInfo `json:"sync_info,omitempty"` ValidatorInfo ValidatorInfo `json:"validator_info,omitempty"` } + +// Used to parse response from tendermint api ("/status") +type AppBridgeRegistrar struct { + Abr uint64 `json:"abr,omitempty"` +}