├── LICENSE ├── README.md ├── cess_client.yaml ├── cessctl ├── ctl │ └── ctl.go └── main.go ├── client ├── file.go ├── purchase.go └── query.go ├── command ├── file_command.go ├── global_flags.go ├── purchase_command.go └── query_command.go ├── conf ├── conf_file.go └── default.go ├── go.mod ├── install_ctl ├── build.bat ├── build.sh ├── cess_client.yaml └── install-cessctl.sh ├── internal ├── chain │ ├── chainstate.go │ ├── events.go │ ├── main.go │ ├── transPram.go │ └── transaction.go ├── logger │ └── logger.go └── rpc │ ├── client.go │ ├── client_test.go │ ├── codec.go │ ├── conn.go │ ├── errors.go │ ├── handler.go │ ├── msg.pb.go │ ├── msg.proto │ ├── server.go │ ├── service.go │ └── websocket.go ├── module ├── file.pb.go ├── file.proto └── services.go ├── test ├── file_decrypt_test.go ├── file_delete_test.go ├── file_download_test.go ├── file_upload_test.go ├── purchase_free_test.go ├── purchase_storage_test.go ├── query_file_test.go ├── query_price_test.go └── query_space_test.go └── tools ├── aes.go └── tool.go /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **CESS-Portal** 2 | 3 | cess-portal is the client of the cess project. By using some simple commands of cess-portal, you can easily realize a series of operations such as purchasing space, querying space, uploading/downloading files, and querying file information on the Linux system. 4 | 5 | # Prerequisites 6 | 7 | * :one: Centos 8 | * :two: Go 1.17 and above 9 | 10 | # **Build Code** 11 | 12 | If you don't have git software on your machine, please install it first 13 | 14 | ```shell 15 | yum install git -y 16 | ``` 17 | 18 | First you need to download the cess-portal project from GitHub 19 | 20 | ```shell 21 | git clone https://github.com/CESSProject/cess-portal.git 22 | ``` 23 | 24 | Then run the build.sh(On Linux) or build.bat(On Windows) script file in the ‘install_ctl‘ folder,You can compile this project on any system,Before downloading, please install golang on the system and the version must be over 1.17. 25 | 26 | ```shell 27 | ##Compile with script 28 | cd /cess-portal/install_ctl 29 | 30 | ##Run it on linux platform 31 | sh build.sh 32 | ``` 33 | 34 | Finally, you can place the 'install_ctl' folder in your Linux environment,you can also operate directly in this folder 35 | 36 | # **Install On Linux** 37 | 38 | ```shell 39 | #If you are not in the install_ctl folder, please enter first 40 | cd install_ctl 41 | ##Provide run permission 42 | chmod 777 install-cessctl.sh 43 | ##Configure a one-click install script 44 | vim install-cessctl.sh 45 | ``` 46 | 47 | Let me introduce the content of the configuration file of the one-click installation script. 48 | 49 | ```shell 50 | ##The log file of the client's operation output, your operation results will be recorded in the output.log file under this file 51 | boardPath='/root' 52 | ##The mailing address of the CESS chain 53 | cessRpcAddr='ws://xxx.xx.xx.xxx:9949/' 54 | ##tCESS pick-up tap address 55 | faucetAddress='http://xx.xxx.xx.xx:9708/transfer' 56 | ##Memo Seed for Wallet 57 | idAccountPhraseOrSeed='lazy funny invest opinion jaguar romance anger return glare flat lift clap' 58 | ##wallet address 59 | walletAddress='5AhdZVDwjFXpvbsTjHaXv2jqNos49zFFnb5K4A1hnzVSo1iR' 60 | ##If the file upload is encrypted, the password memo of the file will be saved here, and it can be created to the next directory of the existing folder. 61 | keyPath='/root/keypath' 62 | ##The path address of the file download, the downloaded files will appear here, support to create the next level directory of the existing folder 63 | installPath='/root/cessDownload' 64 | ``` 65 | 66 | Please edit the configuration of the above file, press the ESC key on the keyboard and enter': wq', then press the Enter key on keyboard for save it.Next you can run the script to install. 67 | 68 | ```shell 69 | ./install-cessctl.sh 70 | ``` 71 | 72 | # **Getting Started** 73 | 74 | ## **Command group** 75 | 76 | | command group name | subcommand name | features | 77 | | ------------------ | --------------- | ------------------------------------------------------------ | 78 | | query | price | Query the current storage price per MB | 79 | | query | space | Query currently used space, purchased space, remaining space | 80 | | query | file | Query file or file list | 81 | | file | upload | upload files | 82 | | file | download | download file | 83 | | file | delete | delete file | 84 | | file | decrypt | decrypt encrypted files | 85 | | purchase | storage | buy storage | 86 | | purchase | free | Get coins from the faucet | 87 | 88 | 89 | 90 | ## **Global command** 91 | 92 | -h,--help:Get the specific operation method of the command line 93 | 94 | -c,--config:Absolute path, the address of the configuration file; used when not defined:/etc/cess.d/cess_client.yaml 95 | 96 | 97 | 98 | ## **Configuration file** 99 | 100 | boardPath:Absolute path, the data kanban location of the result output; if not defined, output to: /etc/cess.d file. 101 | 102 | cessRpcAddr:Chain interaction address, the address that interacts with the chain. 103 | 104 | faucetAddress:Faucet address, the address to get coins from the faucet. 105 | 106 | idAccountPhraseOrSeed:Account private key, which is used as the user's mnemonic when signing transactions. 107 | 108 | walletAddress:The wallet public key address, the owner id of the file when uploading the file metadata. 109 | 110 | 111 | 112 | ## **Operate example** 113 | 114 | ### (A)Query storage unit price 115 | 116 | * instruction: 117 | 118 | ​ Chain query and displays the current lease storage space Price (Unit: TCess / GB) 119 | 120 | * usage: 121 | 122 | ​ cessctl query price 123 | 124 | * example: 125 | 126 | ​ cessctl query price 127 | 128 | 129 | 130 | ### (B)Check remaining space 131 | 132 | * instruction: 133 | 134 | ​ Chain query current account purchased storage space usage (used and remaining) 135 | 136 | * usage: 137 | 138 | ​ cessctl query space 139 | 140 | * example: 141 | 142 | ​ cessctl query space 143 | 144 | 145 | 146 | ### (C)Query file information 147 | 148 | * instruction: 149 | 150 | ​ Chain query all file information that has been uploaded by the current account (sorting, keyword retrieval...) 151 | 152 | * usage: 153 | 154 | ​ cessctl query file 155 | 156 | ​ If fileid is vaild, output all uploaded file information of the user in the configuration file 157 | 158 | * example: 159 | 160 | ​ Query single file information:cessctl query file 1483720947931287552 161 | 162 | ​ Query file list information:cessctl query file 163 | 164 | 165 | 166 | ### (D)Upload files 167 | 168 | * instruction: 169 | 170 | ​ Send local source files to scheduling nodes 171 | 172 | * usage: 173 | 174 | ​ cessctl file upload 175 | 176 | ​ file path:The absolute path of the file, not a folder 177 | 178 | ​ backups:The number of backups of uploaded files in the CESS system. The more the number of backups, the more secure it is and the more space it consumes 179 | 180 | * example: 181 | 182 | ​ cessctl file upload /root/test.txt 3 183 | 184 | 185 | 186 | ### (E)Download file 187 | 188 | * instruction: 189 | 190 | ​ Download file based on fileid,Download to the installPath path in the configuration file 191 | 192 | * usage: 193 | 194 | ​ cessctl file download 195 | 196 | ​ fileid:The unique id of the file 197 | 198 | * example: 199 | 200 | ​ cessctl file download 1483720947931287552 201 | 202 | 203 | 204 | ### (F)Buy space 205 | 206 | * instruction: 207 | 208 | ​ Send on-chain transactions, buy space 209 | 210 | * usage: 211 | 212 | ​ cessctl purchase storage 213 | 214 | ​ space quantity:The number of expansion capacity, unit: 1/1GB 215 | 216 | ​ space duration:You want to buy space for several time units, unit: 1/1month 217 | 218 | ​ expected price:The maximum acceptable price for buying space, in cess; if it is empty, all prices are accepted 219 | 220 | * example: 221 | 222 | ​ expected price 20cess:cessctl purchase storage 1 1 20 223 | 224 | ​ All price accepted:cessctl purchase storage 1 1 225 | 226 | 227 | 228 | ### (Y)Tap to get tokens 229 | 230 | * instruction: 231 | 232 | ​ Get a certain amount of tokens through the faucet service 233 | 234 | * usage: 235 | 236 | ​ cessctl purchase free 237 | 238 | ​ address:wallet address 239 | 240 | * example: 241 | 242 | ​ cessctl purchase free cXjsAyird2dizRjmHML9Eqxp1MGodGdEUHv8rjr7z56Dv5A7C 243 | 244 | 245 | 246 | ### (T)File delete 247 | 248 | * instruction: 249 | 250 | ​ Delete file meta information. 251 | 252 | * usage: 253 | 254 | ​ cessctl file delete 255 | 256 | ​ fileid:file unique id 257 | 258 | * example: 259 | 260 | ​ cessctl file delete 1506154108548026368 261 | 262 | ### (L)File decrypt 263 | 264 | * instruction: 265 | 266 | ​ Decrypt the files that have not been decrypted, and the decrypted files will be stored in the 'installPath' path of the configuration file. 267 | 268 | * usage: 269 | 270 | ​ cessctl file decrypt 271 | 272 | ​ filepath:path to the file that needs to be decrypted 273 | 274 | * example: 275 | 276 | ​ cessctl file decrypt /root/test.txt 277 | -------------------------------------------------------------------------------- /cess_client.yaml: -------------------------------------------------------------------------------- 1 | boardInfo: 2 | boardPath : "" 3 | 4 | chainData: 5 | cessRpcAddr : "" 6 | faucetAddress : "" 7 | idAccountPhraseOrSeed : "" 8 | walletAddress : "" 9 | 10 | pathInfo: 11 | keyPath : "" 12 | installPath : "" -------------------------------------------------------------------------------- /cessctl/ctl/ctl.go: -------------------------------------------------------------------------------- 1 | package ctl 2 | 3 | import ( 4 | "cess-portal/command" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | const ( 9 | Name = "cessctl" 10 | Description = "Cess client is used by entering the command line" 11 | ) 12 | 13 | var ( 14 | rootCmd = &cobra.Command{ 15 | Use: Name, 16 | Short: Description, 17 | SuggestFor: []string{"cessctl"}, 18 | } 19 | globalFlag = command.GlobalFlags{} 20 | err error 21 | ) 22 | 23 | func init() { 24 | rootCmd.PersistentFlags().StringVarP(&globalFlag.ConfFilePath, "config", "c", "", "Custom configuration file path, requires absolute path") 25 | 26 | rootCmd.AddCommand( 27 | command.NewQueryCommand(), 28 | command.NewFileCommand(), 29 | command.NewPurchaseCommand(), 30 | ) 31 | } 32 | func Start() error { 33 | rootCmd.CompletionOptions.HiddenDefaultCmd = true 34 | return rootCmd.Execute() 35 | } 36 | -------------------------------------------------------------------------------- /cessctl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "cess-portal/cessctl/ctl" 5 | ) 6 | 7 | func main() { 8 | ctl.Start() 9 | } 10 | -------------------------------------------------------------------------------- /client/file.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "cess-portal/conf" 5 | "cess-portal/internal/chain" 6 | "cess-portal/internal/logger" 7 | "cess-portal/internal/rpc" 8 | "cess-portal/module" 9 | "cess-portal/tools" 10 | "context" 11 | "errors" 12 | "fmt" 13 | "github.com/btcsuite/btcutil/base58" 14 | "github.com/howeyc/gopass" 15 | "google.golang.org/protobuf/proto" 16 | "io/ioutil" 17 | "math/big" 18 | "os" 19 | "path/filepath" 20 | "strconv" 21 | "sync" 22 | "time" 23 | ) 24 | 25 | /* 26 | FileUpload means upload files to CESS system 27 | path:The absolute path of the file to be uploaded 28 | backups:Number of backups of files that need to be uploaded 29 | PrivateKey:Encrypted password for uploaded files 30 | */ 31 | func FileUpload(path, backups, PrivateKey string) error { 32 | if len(PrivateKey) != 16 && len(PrivateKey) != 24 && len(PrivateKey) != 32 && len(PrivateKey) != 0 { 33 | fmt.Printf("[Error]The privatekey must be 16,24,32 bits long\n") 34 | return errors.New("[Error]The privatekey must be 16,24,32 bits long") 35 | } 36 | if backups == "0" { 37 | fmt.Printf("[Error]The number of backups must be bigger than 1\n") 38 | return errors.New("The number of backups must be bigger than 1") 39 | } 40 | chain.Chain_Init() 41 | file, err := os.Stat(path) 42 | if err != nil { 43 | fmt.Printf("[Error]Please enter the correct file path!\n") 44 | return err 45 | } 46 | 47 | if file.IsDir() { 48 | fmt.Printf("[Error]Please do not upload the folder!\n") 49 | return err 50 | } 51 | 52 | spares, err := strconv.Atoi(backups) 53 | if err != nil { 54 | fmt.Printf("[Error]Please enter a correct integer!\n") 55 | return err 56 | } 57 | if spares > conf.MaxBackups { 58 | fmt.Printf("[warm]The maximum number of backups is %v, it has been set to %v backups for you.\n", conf.MaxBackups, conf.MaxBackups) 59 | } 60 | 61 | filehash, err := tools.CalcFileHash(path) 62 | if err != nil { 63 | fmt.Printf("[Error]There is a problem with the file, please replace it!\n") 64 | return err 65 | } 66 | 67 | fileid, err := tools.GetGuid(1) 68 | if err != nil { 69 | fmt.Printf("[Error]Create snowflake fail! error:%s\n", err) 70 | return err 71 | } 72 | var blockinfo module.FileUploadInfo 73 | blockinfo.Backups = backups 74 | blockinfo.FileId = fileid 75 | blockinfo.BlockSize = int32(file.Size()) 76 | blockinfo.FileHash = filehash 77 | 78 | blocksize := 1024 * 1024 79 | blocktotal := 0 80 | 81 | f, err := os.Open(path) 82 | if err != nil { 83 | fmt.Println("[Error]This file was broken!\n ", err) 84 | return err 85 | } 86 | defer f.Close() 87 | filebyte, err := ioutil.ReadAll(f) 88 | if err != nil { 89 | fmt.Println("[Error]analyze this file error!\n ", err) 90 | return err 91 | } 92 | 93 | var ci chain.CessInfo 94 | ci.RpcAddr = conf.ClientConf.ChainData.CessRpcAddr 95 | ci.ChainModule = chain.FindSchedulerInfoModule 96 | ci.ChainModuleMethod = chain.FindSchedulerInfoMethod 97 | schds, err := ci.GetSchedulerInfo() 98 | if err != nil { 99 | fmt.Println("[Error]Get scheduler randomly error!\n ", err) 100 | return err 101 | } 102 | //var filesize uint64 103 | fee := new(big.Int) 104 | 105 | ci.IdentifyAccountPhrase = conf.ClientConf.ChainData.IdAccountPhraseOrSeed 106 | ci.TransactionName = chain.UploadFileTransactionName 107 | 108 | fee.SetInt64(int64(0)) 109 | 110 | FileUploadStatus, err := ci.UploadFileMetaInformation(fileid, file.Name(), filehash, PrivateKey == "", uint8(spares), uint64(file.Size()), fee) 111 | if err != nil { 112 | fmt.Printf("[Error]Upload file meta information fail\n") 113 | logger.OutPutLogger.Sugar().Infof("[Error]Upload file meta information error:%s\n", err) 114 | return err 115 | } 116 | fmt.Printf("File meta info upload:%s ,fileid is:%s\n", FileUploadStatus, fileid) 117 | 118 | var client *rpc.Client 119 | for i, schd := range schds { 120 | wsURL := "ws://" + string(base58.Decode(string(schd.Ip))) 121 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 122 | client, err = rpc.DialWebsocket(ctx, wsURL, "") 123 | defer cancel() 124 | if err != nil { 125 | err = errors.New("Connect with scheduler timeout") 126 | if i == len(schds)-1 { 127 | fmt.Printf("%s[Error]All scheduler is offline!!!%s\n", tools.Red, tools.Reset) 128 | logger.OutPutLogger.Sugar().Infof("\n%s[Error]All scheduler is offlien!!!%s\n", tools.Red, tools.Reset) 129 | return err 130 | } 131 | continue 132 | } else { 133 | break 134 | } 135 | } 136 | sp := sync.Pool{ 137 | New: func() interface{} { 138 | return &rpc.ReqMsg{} 139 | }, 140 | } 141 | commit := func(num int, data []byte) error { 142 | blockinfo.BlockIndex = int32(num) + 1 143 | blockinfo.Data = data 144 | info, err := proto.Marshal(&blockinfo) 145 | if err != nil { 146 | fmt.Println("[Error]There was a problem with the network during upload, please upload again!\n") 147 | logger.OutPutLogger.Sugar().Infof("[Error]There was a problem with the network during upload, please upload again!error:%s", err) 148 | return err 149 | } 150 | reqmsg := sp.Get().(*rpc.ReqMsg) 151 | reqmsg.Body = info 152 | reqmsg.Method = module.UploadService 153 | reqmsg.Service = module.CtlServiceName 154 | 155 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 156 | resp, err := client.Call(ctx, reqmsg) 157 | defer cancel() 158 | if err != nil { 159 | fmt.Printf("%s[Error]Failed to transfer file to scheduler%s\n", tools.Red, tools.Reset) 160 | logger.OutPutLogger.Sugar().Infof("%s[Error]Failed to transfer file to scheduler,error:%s%s\n", tools.Red, err, tools.Reset) 161 | return err 162 | } 163 | 164 | var res rpc.RespBody 165 | err = proto.Unmarshal(resp.Body, &res) 166 | if err != nil { 167 | fmt.Printf("[Error]response error from scheduler,upload failed\n") 168 | logger.OutPutLogger.Sugar().Infof("[Error]response error from scheduler,upload failed!error:%s ", err) 169 | return err 170 | } 171 | if res.Code != 200 { 172 | fmt.Printf("[Error]Upload file fail!scheduler problem:%s\n", res.Msg) 173 | logger.OutPutLogger.Sugar().Infof("[Error]Upload file fail!scheduler problem:%s\n", res.Msg) 174 | os.Exit(conf.Exit_SystemErr) 175 | } 176 | sp.Put(reqmsg) 177 | return nil 178 | } 179 | 180 | if len(PrivateKey) != 0 { 181 | _, err = os.Stat(conf.ClientConf.PathInfo.KeyPath) 182 | if err != nil { 183 | err = os.Mkdir(conf.ClientConf.PathInfo.KeyPath, os.ModePerm) 184 | if err != nil { 185 | fmt.Printf("%s[Error]Create key path %s\n", tools.Red, tools.Reset) 186 | logger.OutPutLogger.Sugar().Infof("%s[Error]Create key path error :%s%s\n", tools.Red, err, tools.Reset) 187 | os.Exit(conf.Exit_SystemErr) 188 | } 189 | } 190 | 191 | os.Create(filepath.Join(conf.ClientConf.PathInfo.KeyPath, file.Name()) + ".pem") 192 | keyfile, err := os.OpenFile(filepath.Join(conf.ClientConf.PathInfo.KeyPath, file.Name())+".pem", os.O_WRONLY|os.O_APPEND, 0666) 193 | if err != nil { 194 | fmt.Printf("%s[Error]:Failed to save key%s\n", tools.Red, tools.Reset) 195 | logger.OutPutLogger.Sugar().Infof("%s[Error]:Failed to save key%s error:%s\n", tools.Red, tools.Reset, err) 196 | return err 197 | } 198 | _, err = keyfile.WriteString(PrivateKey) 199 | if err != nil { 200 | fmt.Printf("%s[Error]:Failed to write key to file:%s%s\n", tools.Red, filepath.Join(conf.ClientConf.PathInfo.KeyPath, (file.Name()+".pem")), tools.Reset) 201 | logger.OutPutLogger.Sugar().Infof("%s[Error]:Failed to write key to file:%s%s error:%s", tools.Red, filepath.Join(conf.ClientConf.PathInfo.KeyPath, (file.Name()+".pem")), tools.Reset, err) 202 | return err 203 | } 204 | 205 | encodefile, err := tools.AesEncrypt(filebyte, []byte(PrivateKey)) 206 | if err != nil { 207 | fmt.Println("[Error]Encode the file fail ,error!\n ") 208 | logger.OutPutLogger.Sugar().Infof("[Error]Encode the file fail ,error:%s\n ", err) 209 | return err 210 | } 211 | blocks := len(encodefile) / blocksize 212 | if len(encodefile)%blocksize == 0 { 213 | blocktotal = blocks 214 | } else { 215 | blocktotal = blocks + 1 216 | } 217 | blockinfo.BlockTotal = int32(blocktotal) 218 | var bar tools.Bar 219 | bar.NewOption(0, int64(blocktotal)) 220 | for i := 0; i < blocktotal; i++ { 221 | block := make([]byte, 0) 222 | if blocks != i { 223 | block = encodefile[i*blocksize : (i+1)*blocksize] 224 | bar.Play(int64(i + 1)) 225 | } else { 226 | block = encodefile[i*blocksize:] 227 | bar.Play(int64(i + 1)) 228 | } 229 | err = commit(i, block) 230 | if err != nil { 231 | bar.Finish() 232 | fmt.Printf("%s[Error]File upload failed, network error%s\n", tools.Red, tools.Reset) 233 | logger.OutPutLogger.Sugar().Infof("%s[Error]File upload failed, network error:%s%s\n", tools.Red, err, tools.Reset) 234 | return err 235 | } 236 | } 237 | bar.Finish() 238 | } else { 239 | fmt.Printf("%s[Tips]%s:upload file:%s without private key\n", tools.Yellow, tools.Reset, path) 240 | blocks := len(filebyte) / blocksize 241 | if len(filebyte)%blocksize == 0 { 242 | blocktotal = blocks 243 | } else { 244 | blocktotal = blocks + 1 245 | } 246 | blockinfo.BlockTotal = int32(blocktotal) 247 | var bar tools.Bar 248 | bar.NewOption(0, int64(blocktotal)) 249 | for i := 0; i < blocktotal; i++ { 250 | block := make([]byte, 0) 251 | if blocks != i { 252 | block = filebyte[i*blocksize : (i+1)*blocksize] 253 | bar.Play(int64(i + 1)) 254 | } else { 255 | block = filebyte[i*blocksize:] 256 | bar.Play(int64(i + 1)) 257 | } 258 | err = commit(i, block) 259 | if err != nil { 260 | bar.Finish() 261 | fmt.Printf("%s[Error]File upload failed, network error%s\n", tools.Red, tools.Reset) 262 | logger.OutPutLogger.Sugar().Infof("%s[Error]File upload failed, network error:%s%s\n", tools.Red, err, tools.Reset) 263 | return err 264 | } 265 | } 266 | bar.Finish() 267 | } 268 | fmt.Printf("%s[Success]%s:upload file:%s successful!\n", tools.Green, tools.Reset, path) 269 | return nil 270 | } 271 | 272 | /* 273 | FileDownload means download file by file id 274 | fileid:fileid of the file to download 275 | */ 276 | func FileDownload(fileid string) error { 277 | chain.Chain_Init() 278 | var ci chain.CessInfo 279 | ci.RpcAddr = conf.ClientConf.ChainData.CessRpcAddr 280 | ci.ChainModule = chain.FindFileChainModule 281 | ci.ChainModuleMethod = chain.FindFileModuleMethod[0] 282 | fileinfo, err := ci.GetFileInfo(fileid) 283 | if err != nil { 284 | fmt.Printf("%s[Error]Get file:%s info fail:%s%s\n", tools.Red, fileid, err, tools.Reset) 285 | logger.OutPutLogger.Sugar().Infof("%s[Error]Get file:%s info fail:%s%s\n", tools.Red, fileid, err, tools.Reset) 286 | return err 287 | } 288 | if fileinfo.File_Name == nil { 289 | fmt.Printf("%s[Error]The fileid:%s has been deleted,the file does not exist%s\n", tools.Red, fileid, tools.Reset) 290 | logger.OutPutLogger.Sugar().Infof("%s[Error]The fileid:%s has been deleted,the file does not exist%s\n", tools.Red, fileid, tools.Reset) 291 | return err 292 | } 293 | if string(fileinfo.FileState) != "active" { 294 | fmt.Printf("%s[Tips]The file:%s has not been backed up, please try again later%s\n", tools.Yellow, fileid, tools.Reset) 295 | logger.OutPutLogger.Sugar().Infof("%s[Tips]The file:%s has not been backed up, please try again later%s\n", tools.Yellow, fileid, tools.Reset) 296 | return err 297 | } 298 | 299 | _, err = os.Stat(conf.ClientConf.PathInfo.InstallPath) 300 | if err != nil { 301 | err = os.Mkdir(conf.ClientConf.PathInfo.InstallPath, os.ModePerm) 302 | if err != nil { 303 | fmt.Printf("%s[Error]Create install path error %s\n", tools.Red, tools.Reset) 304 | logger.OutPutLogger.Sugar().Infof("%s[Error]Create install path error :%s%s\n", tools.Red, err, tools.Reset) 305 | os.Exit(conf.Exit_SystemErr) 306 | } 307 | } 308 | _, err = os.Create(filepath.Join(conf.ClientConf.PathInfo.InstallPath, string(fileinfo.File_Name[:]))) 309 | if err != nil { 310 | fmt.Printf("%s[Error]Create installed file error %s\n", tools.Red, tools.Reset) 311 | logger.OutPutLogger.Sugar().Infof("%s[Error]Create installed file error :%s%s\n", tools.Red, err, tools.Reset) 312 | os.Exit(conf.Exit_SystemErr) 313 | } 314 | installfile, err := os.OpenFile(filepath.Join(conf.ClientConf.PathInfo.InstallPath, string(fileinfo.File_Name[:])), os.O_WRONLY|os.O_APPEND, 0666) 315 | if err != nil { 316 | fmt.Printf("%s[Error]:Failed to save key error%s\n", tools.Red, tools.Reset) 317 | logger.OutPutLogger.Sugar().Infof("%s[Error]:Failed to save key error:%s%s", tools.Red, err, tools.Reset) 318 | return err 319 | } 320 | defer installfile.Close() 321 | 322 | ci.RpcAddr = conf.ClientConf.ChainData.CessRpcAddr 323 | ci.ChainModule = chain.FindSchedulerInfoModule 324 | ci.ChainModuleMethod = chain.FindSchedulerInfoMethod 325 | schds, err := ci.GetSchedulerInfo() 326 | if err != nil { 327 | fmt.Printf("%s[Error]Get scheduler list error%s\n ", tools.Red, tools.Reset) 328 | logger.OutPutLogger.Sugar().Infof("%s[Error]Get scheduler list error:%s%s\n ", tools.Red, err, tools.Reset) 329 | return err 330 | } 331 | 332 | var client *rpc.Client 333 | for i, schd := range schds { 334 | wsURL := "ws://" + string(base58.Decode(string(schd.Ip))) 335 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 336 | client, err = rpc.DialWebsocket(ctx, wsURL, "") 337 | defer cancel() 338 | if err != nil { 339 | err = errors.New("Connect with scheduler timeout\n") 340 | if i == len(schds)-1 { 341 | fmt.Printf("%s[Error]All scheduler is offline!!!%s\n", tools.Red, tools.Reset) 342 | return err 343 | } 344 | continue 345 | } else { 346 | break 347 | } 348 | } 349 | 350 | var wantfile module.FileDownloadReq 351 | var bar tools.Bar 352 | var getAllBar sync.Once 353 | sp := sync.Pool{ 354 | New: func() interface{} { 355 | return &rpc.ReqMsg{} 356 | }, 357 | } 358 | wantfile.FileId = fileid 359 | wantfile.WalletAddress = conf.ClientConf.ChainData.WalletAddress 360 | wantfile.BlockIndex = 1 361 | 362 | for { 363 | data, err := proto.Marshal(&wantfile) 364 | if err != nil { 365 | fmt.Printf("[Error]Marshal req file error\n") 366 | logger.OutPutLogger.Sugar().Infof("[Error]Marshal req file error:%s\n", err) 367 | return err 368 | } 369 | req := sp.Get().(*rpc.ReqMsg) 370 | req.Method = module.DownloadService 371 | req.Service = module.CtlServiceName 372 | req.Body = data 373 | 374 | ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) 375 | resp, err := client.Call(ctx, req) 376 | cancel() 377 | if err != nil { 378 | fmt.Printf("[Error]Download file fail error\n") 379 | logger.OutPutLogger.Sugar().Infof("[Error]Download file fail error:%s\n", err) 380 | return err 381 | } 382 | 383 | var respbody rpc.RespBody 384 | err = proto.Unmarshal(resp.Body, &respbody) 385 | if err != nil || respbody.Code != 200 { 386 | fmt.Printf("[Error]Download file from CESS fail\n") 387 | logger.OutPutLogger.Sugar().Infof("[Error]Download file from CESS error:%v. reply message:%s\n", err, respbody.Msg) 388 | return err 389 | } 390 | var blockData module.FileDownloadInfo 391 | err = proto.Unmarshal(respbody.Data, &blockData) 392 | if err != nil { 393 | fmt.Printf("[Error]Download file from CESS error\n") 394 | logger.OutPutLogger.Sugar().Infof("[Error]Download file from CESS error:%s\n", err) 395 | return err 396 | } 397 | 398 | _, err = installfile.Write(blockData.Data) 399 | if err != nil { 400 | fmt.Printf("%s[Error]Failed to write file's block to file:%s%s error:%s\n", tools.Red, filepath.Join(conf.ClientConf.PathInfo.InstallPath, string(fileinfo.File_Name[:])), tools.Reset) 401 | logger.OutPutLogger.Sugar().Infof("%s[Error]:Failed to write file's block to file:%s%s error:%s", tools.Red, filepath.Join(conf.ClientConf.PathInfo.InstallPath, string(fileinfo.File_Name[:])), tools.Reset, err) 402 | return err 403 | } 404 | 405 | getAllBar.Do(func() { 406 | bar.NewOption(0, int64(blockData.BlockTotal)) 407 | }) 408 | bar.Play(int64(blockData.BlockIndex)) 409 | wantfile.BlockIndex++ 410 | sp.Put(req) 411 | if blockData.BlockIndex == blockData.BlockTotal { 412 | break 413 | } 414 | } 415 | 416 | bar.Finish() 417 | fmt.Printf("%s[OK]:File '%s' has been downloaded to the directory :%s%s\n", tools.Green, string(fileinfo.File_Name), filepath.Join(conf.ClientConf.PathInfo.InstallPath, string(fileinfo.File_Name[:])), tools.Reset) 418 | 419 | if !fileinfo.Public { 420 | fmt.Printf("%s[Warm]This is a private file, please enter the file password(If you don't want to decrypt, just press enter):%s\n", tools.Green, tools.Reset) 421 | fmt.Printf("Password:") 422 | filePWD, _ := gopass.GetPasswdMasked() 423 | if len(filePWD) == 0 { 424 | return nil 425 | } 426 | if len(filePWD) != 16 && len(filePWD) != 24 && len(filePWD) != 32 { 427 | fmt.Printf("%s[Error]The password must be 16,24,32 bits long,your password length is :%v%s\n", tools.Red, len(filePWD), tools.Reset) 428 | return errors.New("[Error]The password must be 16,24,32 bits long") 429 | } 430 | encodefile, err := ioutil.ReadFile(filepath.Join(conf.ClientConf.PathInfo.InstallPath, string(fileinfo.File_Name[:]))) 431 | if err != nil { 432 | fmt.Printf("%s[Error]:Decode file:%s fail%s\n", tools.Red, filepath.Join(conf.ClientConf.PathInfo.InstallPath, string(fileinfo.File_Name[:])), tools.Reset) 433 | logger.OutPutLogger.Sugar().Infof("%s[Error]:Decode file:%s fail%s error:%s\n", tools.Red, filepath.Join(conf.ClientConf.PathInfo.InstallPath, string(fileinfo.File_Name[:])), tools.Reset, err) 434 | return err 435 | } 436 | decryptfile, err := tools.AesDecrypt(encodefile, filePWD) 437 | if err != nil { 438 | fmt.Println("[Error]Dncode the file fail,Incorrect password\n ", err) 439 | fmt.Println("[Error]Dncode the file fail ,error:%s\n ", err) 440 | return err 441 | } 442 | err = installfile.Truncate(0) 443 | _, err = installfile.Seek(0, os.SEEK_SET) 444 | _, err = installfile.Write(decryptfile[:]) 445 | } 446 | 447 | return nil 448 | } 449 | 450 | /* 451 | FileDelete means to delete the file from the CESS system by the file id 452 | fileid:fileid of the file that needs to be deleted 453 | */ 454 | func FileDelete(fileid string) error { 455 | chain.Chain_Init() 456 | var ci chain.CessInfo 457 | ci.RpcAddr = conf.ClientConf.ChainData.CessRpcAddr 458 | ci.IdentifyAccountPhrase = conf.ClientConf.ChainData.IdAccountPhraseOrSeed 459 | ci.TransactionName = chain.DeleteFileTransactionName 460 | 461 | err := ci.DeleteFileOnChain(fileid) 462 | if err != nil { 463 | fmt.Printf("%s[Error]Delete file error%s\n", tools.Red, tools.Reset) 464 | logger.OutPutLogger.Sugar().Infof("%s[Error]Delete file error:%s%s\n", tools.Red, err, tools.Reset) 465 | return err 466 | } else { 467 | fmt.Printf("%s[OK]Delete fileid:%s success!%s\n", tools.Green, fileid, tools.Reset) 468 | logger.OutPutLogger.Sugar().Infof("%s[OK]Delete fileid:%s success!%s\n", tools.Green, fileid, tools.Reset) 469 | return nil 470 | } 471 | 472 | } 473 | 474 | /* 475 | FileDecrypt means that if the file is not decrypted when downloading the file, it can be decrypted by this method 476 | When you download the file if it is not decrypt, you can decrypt it this way 477 | */ 478 | func FileDecrypt(path string) error { 479 | _, err := os.Stat(path) 480 | if err != nil { 481 | fmt.Printf("%s[Error]There is no such file, please confirm the correct location of the file, please enter the absolute path of the file%s\n", tools.Red, tools.Reset) 482 | logger.OutPutLogger.Sugar().Infof("%s[Error]There is no such file, please confirm the correct location of the file, please enter the absolute path of the file%s\n", tools.Red, tools.Reset) 483 | return err 484 | } 485 | 486 | fmt.Println("Please enter the file's password:") 487 | fmt.Print(">") 488 | psw, _ := gopass.GetPasswdMasked() 489 | if len(psw) != 16 && len(psw) != 24 && len(psw) != 32 { 490 | fmt.Printf("%s[Error]The password must be 16,24,32 bits long,your password length is :%v%s\n", tools.Red, len(psw), tools.Reset) 491 | return errors.New("[Error]The password must be 16,24,32 bits long") 492 | } 493 | encodefile, err := ioutil.ReadFile(path) 494 | if err != nil { 495 | fmt.Printf("%s[Error]Failed to read file, please check file integrity%s\n", tools.Red, tools.Reset) 496 | logger.OutPutLogger.Sugar().Infof("%s[Error]Failed to read file, please check file integrity%s\n", tools.Red, tools.Reset) 497 | return err 498 | } 499 | 500 | decryptfile, err := tools.AesDecrypt(encodefile, psw) 501 | if err != nil { 502 | fmt.Printf("%s[Error]File decrypt failed, please check your password!%s\n", tools.Red, tools.Reset) 503 | logger.OutPutLogger.Sugar().Infof("%s[Error]File decrypt failed, please check your password! error:%s%s ", tools.Red, err, tools.Reset) 504 | return err 505 | } 506 | filename := filepath.Base(path) 507 | //The decrypted file is saved to the download folder, if the name is the same, the original file will be deleted 508 | if path == filepath.Join(conf.ClientConf.PathInfo.InstallPath, filename) { 509 | err = os.Remove(path) 510 | if err != nil { 511 | fmt.Printf("%s[Error]An error occurred while saving the decrypted file!%s\n ", tools.Red, tools.Reset) 512 | logger.OutPutLogger.Sugar().Infof("%s[Error]An error occurred while saving the decrypted file! error:%s%s ", tools.Red, err, tools.Reset) 513 | return err 514 | } 515 | } 516 | fileinfo, err := os.Create(filepath.Join(conf.ClientConf.PathInfo.InstallPath, filename)) 517 | if err != nil { 518 | fmt.Printf("%s[Error]An error occurred while saving the decrypted file!%s\n ", tools.Red, tools.Reset) 519 | logger.OutPutLogger.Sugar().Infof("%s[Error]An error occurred while saving the decrypted file! error:%s%s\n ", tools.Red, err, tools.Reset) 520 | return err 521 | } 522 | defer fileinfo.Close() 523 | _, err = fileinfo.Write(decryptfile) 524 | if err != nil { 525 | fmt.Printf("%s[Error]Failed to save decrypted content to file!%s\n ", tools.Red, tools.Reset) 526 | logger.OutPutLogger.Sugar().Infof("%s[Error]Failed to save decrypted content to file! error:%s%s\n ", tools.Red, err, tools.Reset) 527 | return err 528 | } 529 | 530 | fmt.Printf("%s[Success]The file was decrypted successfully and the file has been saved to:%s%s\n ", tools.Green, filepath.Join(conf.ClientConf.PathInfo.InstallPath, filename), tools.Reset) 531 | 532 | return nil 533 | } 534 | -------------------------------------------------------------------------------- /client/purchase.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "cess-portal/conf" 5 | "cess-portal/internal/chain" 6 | "cess-portal/internal/logger" 7 | "cess-portal/tools" 8 | "encoding/json" 9 | "fmt" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | type faucet struct { 14 | Ans answer `json:"Result"` 15 | Status string `json:"Status"` 16 | } 17 | type answer struct { 18 | Err string `json:"Err"` 19 | AsInBlock bool `json:"AsInBlock"` 20 | } 21 | 22 | /* 23 | ObtainFromFaucet means to obtain tCESS for transaction spending through the faucet 24 | walletaddress:wallet address 25 | */ 26 | func ObtainFromFaucet(walletaddress string) error { 27 | pubkey, err := tools.DecodeToPub(walletaddress, tools.ChainCessTestPrefix) 28 | if err != nil { 29 | fmt.Printf("[Error]The wallet address you entered is incorrect, please re-enter:%v\n", err.Error()) 30 | return err 31 | } 32 | var ob = struct { 33 | Address string `json:"Address"` 34 | }{ 35 | tools.PubBytesTo0XString(pubkey), 36 | } 37 | var res faucet 38 | resp, err := tools.Post(conf.ClientConf.ChainData.FaucetAddress, ob) 39 | if err != nil { 40 | fmt.Printf("[Error]Network problem, please check your network connection\n") 41 | logger.OutPutLogger.Sugar().Infof("[Error]Network problem, please check your network connection, error:%s\n", err) 42 | return err 43 | } 44 | err = json.Unmarshal(resp, &res) 45 | if err != nil { 46 | fmt.Printf("Incorrect response from faucet\n") 47 | logger.OutPutLogger.Sugar().Infof("Incorrect response from faucet,error:%s", err) 48 | return err 49 | } 50 | if res.Ans.Err != "" { 51 | fmt.Printf("[Error]get free token from faucet fail:%s\n", res.Ans.Err) 52 | logger.OutPutLogger.Sugar().Infof("[Error]get free token from faucet fail:%s\n", res.Ans.Err) 53 | return err 54 | } 55 | 56 | if res.Ans.AsInBlock { 57 | fmt.Printf("[Success]Obtain from faucet success\n") 58 | logger.OutPutLogger.Sugar().Infof("[Success]Obtain from faucet success\n") 59 | } else { 60 | fmt.Printf("[Fail]Obtain from faucet fail,Please wait 24 hours to get it again\n") 61 | logger.OutPutLogger.Sugar().Infof("[Fail]Obtain from faucet fail,Please wait 24 hours to get it again\n") 62 | } 63 | return nil 64 | } 65 | 66 | /* 67 | Expansion means the purchase of storage capacity for the current customer 68 | quantity:The amount of space to be purchased (1/G) 69 | duration:Market for space that needs to be purchased (1/month) 70 | expected:The expected number of prices when buying is required to prevent price fluctuations when buying. When it is 0, it means that any price can be accepted 71 | */ 72 | func Expansion(quantity, duration, expected int) error { 73 | chain.Chain_Init() 74 | if quantity == 0 || duration == 0 { 75 | fmt.Printf("[Error] Please enter the correct purchase number\n") 76 | return errors.New("Please enter the correct purchase number") 77 | } 78 | var ci chain.CessInfo 79 | ci.RpcAddr = conf.ClientConf.ChainData.CessRpcAddr 80 | ci.IdentifyAccountPhrase = conf.ClientConf.ChainData.IdAccountPhraseOrSeed 81 | ci.TransactionName = chain.BuySpaceTransactionName 82 | 83 | //Buying space on-chain, failure could mean running out of money 84 | err := ci.BuySpaceOnChain(quantity, duration, expected/1024) 85 | if err != nil { 86 | fmt.Printf("[Error] Failed to buy space, please check if you have enough money or check if there is enough space on the chain\n") 87 | logger.OutPutLogger.Sugar().Infof("[Error] Failed to buy space, please check if you have enough money or check if there is enough space on the chain,error:%v\n", err) 88 | return err 89 | } 90 | fmt.Printf("[Success]Buy space on chain success!\n") 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /client/query.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "cess-portal/conf" 5 | "cess-portal/internal/chain" 6 | "cess-portal/internal/logger" 7 | "cess-portal/tools" 8 | "fmt" 9 | "strconv" 10 | ) 11 | 12 | /* 13 | QueryPurchasedSpace means to query the space that the current user has purchased and the space that has been used 14 | */ 15 | func QueryPurchasedSpace() error { 16 | chain.Chain_Init() 17 | 18 | var ci chain.CessInfo 19 | ci.RpcAddr = conf.ClientConf.ChainData.CessRpcAddr 20 | ci.ChainModule = chain.PurchasedSpaceChainModule 21 | ci.ChainModuleMethod = chain.PurchasedSpaceModuleMethod 22 | 23 | userinfo, err := ci.UserHoldSpaceDetails() 24 | if err != nil { 25 | fmt.Printf("[Error]Get user data fail:%s\n", err) 26 | logger.OutPutLogger.Sugar().Infof("[Error]Get user data fail:%s\n", err) 27 | return err 28 | } 29 | fmt.Println(userinfo) 30 | return nil 31 | } 32 | 33 | /* 34 | QueryPrice means to get real-time price of storage space 35 | */ 36 | func QueryPrice() error { 37 | chain.Chain_Init() 38 | 39 | var ci chain.CessInfo 40 | ci.RpcAddr = conf.ClientConf.ChainData.CessRpcAddr 41 | ci.ChainModule = chain.FindPriceChainModule 42 | 43 | ci.ChainModuleMethod = chain.FindPriceModuleMethod 44 | Price, err := ci.GetPrice() 45 | if err != nil { 46 | fmt.Printf("%s[Error]%sGet price fail\n", tools.Red, tools.Reset) 47 | logger.OutPutLogger.Sugar().Infof("%s[Error]%sGet price fail::%s\n", tools.Red, tools.Reset, err) 48 | return err 49 | } 50 | PerGB, _ := strconv.ParseFloat(fmt.Sprintf("%.12f", float64(Price.Int64()*int64(1024))/float64(1000000000000)), 64) 51 | fmt.Printf("[Success]The current storage price is:%.12f (TCESS/GB)\n", PerGB) 52 | logger.OutPutLogger.Sugar().Infof("[Success]The current storage price is:%.12f (TCESS/GB)\n", PerGB) 53 | return nil 54 | } 55 | 56 | /* 57 | QueryFile means to query the files uploaded by the current user 58 | fileid:fileid of the file to look for 59 | */ 60 | func QueryFile(fileid string) error { 61 | chain.Chain_Init() 62 | 63 | var ci chain.CessInfo 64 | ci.RpcAddr = conf.ClientConf.ChainData.CessRpcAddr 65 | ci.ChainModule = chain.FindFileChainModule 66 | 67 | if fileid != "" { 68 | ci.ChainModuleMethod = chain.FindFileModuleMethod[0] 69 | data, err := ci.GetFileInfo(fileid) 70 | if err != nil { 71 | fmt.Printf("[Error]Get file:%s info fail\n", fileid) 72 | logger.OutPutLogger.Sugar().Infof("[Error]Get file:%s info fail:%s\n", fileid, err) 73 | return err 74 | } 75 | fmt.Println(data) 76 | if len(data.File_Name) == 0 { 77 | fmt.Printf("%s[Tips]This file:%s may have been deleted by someone%s\n", tools.Yellow, fileid, tools.Reset) 78 | } 79 | } else { 80 | ci.ChainModuleMethod = chain.FindFileModuleMethod[1] 81 | data, err := ci.GetFileList() 82 | if err != nil { 83 | fmt.Printf("[Error]Get file list fail\n") 84 | logger.OutPutLogger.Sugar().Infof("[Error]Get file list fail:%s\n", err) 85 | return err 86 | } 87 | for _, fileinfo := range data { 88 | fmt.Printf("%s\n", string(fileinfo)) 89 | } 90 | } 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /command/file_command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "cess-portal/tools" 7 | "fmt" 8 | "github.com/howeyc/gopass" 9 | "github.com/spf13/cobra" 10 | "os" 11 | ) 12 | 13 | func NewFileCommand() *cobra.Command { 14 | fc := &cobra.Command{ 15 | Use: "file ", 16 | Short: "File commands use for implement related file function operate", 17 | } 18 | 19 | fc.AddCommand(NewFileUploadCommand()) 20 | fc.AddCommand(NewFileDownloadCommand()) 21 | fc.AddCommand(NewFileDeleteCommand()) 22 | fc.AddCommand(NewFileDecryptCommand()) 23 | 24 | return fc 25 | } 26 | 27 | func NewFileUploadCommand() *cobra.Command { 28 | cc := &cobra.Command{ 29 | Use: "upload ", 30 | Short: "Upload the any specific file you want", 31 | Long: `Upload command mean send the local source files to CESS nework scheduling nodes;You can input any 16/24/32 length numbers to be your private key, then others people unable to decrypt your file data. if you choose private key is nil, then system is default you file become public file.`, 32 | 33 | Run: FileUploadCommandFunc, 34 | } 35 | 36 | return cc 37 | } 38 | 39 | func FileUploadCommandFunc(cmd *cobra.Command, args []string) { 40 | InitComponents(cmd) 41 | if len(args) < 2 { 42 | fmt.Printf("Please enter correct parameters 'upload '\n") 43 | os.Exit(conf.Exit_CmdLineParaErr) 44 | } 45 | 46 | fmt.Printf("%s[Warming] Do you want to upload your file without private key (it's means your file status is public)?%s\n", tools.Red, tools.Reset) 47 | fmt.Printf("%sYou can type the 'private key' or enter with nothing to skip it:%s", tools.Red, tools.Reset) 48 | psw, _ := gopass.GetPasswdMasked() 49 | 50 | client.FileUpload(args[0], args[1], string(psw)) 51 | } 52 | 53 | func NewFileDownloadCommand() *cobra.Command { 54 | cc := &cobra.Command{ 55 | Use: "download ", 56 | Short: "Download the any specific file you want", 57 | Long: `Download command mean download file from the CESS networks based on fileId.`, 58 | 59 | Run: FileDownloadCommandFunc, 60 | } 61 | 62 | return cc 63 | } 64 | 65 | func FileDownloadCommandFunc(cmd *cobra.Command, args []string) { 66 | InitComponents(cmd) 67 | if len(args) == 0 { 68 | fmt.Printf("Please enter the fileid of the download file 'file download '\n") 69 | os.Exit(conf.Exit_CmdLineParaErr) 70 | } 71 | 72 | client.FileDownload(args[0]) 73 | } 74 | 75 | func NewFileDeleteCommand() *cobra.Command { 76 | cc := &cobra.Command{ 77 | Use: "delete ", 78 | Short: "Delete the any specific file you want", 79 | Long: `Delete command means removing the file from CESS networks`, 80 | 81 | Run: FileDeleteCommandFunc, 82 | } 83 | 84 | return cc 85 | } 86 | 87 | func FileDeleteCommandFunc(cmd *cobra.Command, args []string) { 88 | InitComponents(cmd) 89 | if len(args) == 0 { 90 | fmt.Printf("Please enter the fileid of the delete file'file delete '\n") 91 | os.Exit(conf.Exit_CmdLineParaErr) 92 | } 93 | client.FileDelete(args[0]) 94 | 95 | } 96 | 97 | func NewFileDecryptCommand() *cobra.Command { 98 | cc := &cobra.Command{ 99 | Use: "decrypt ", 100 | Short: "Decrypt the any specific file again when you failed file decrypt first chance", 101 | Long: `File decode means that if the file is not decrypted when you download it, it can be decrypt by this method.Please enter absolute path.`, 102 | 103 | Run: FileDecryptCommandFunc, 104 | } 105 | 106 | return cc 107 | } 108 | 109 | func FileDecryptCommandFunc(cmd *cobra.Command, args []string) { 110 | InitComponents(cmd) 111 | 112 | if len(args) == 0 { 113 | fmt.Printf("Please enter the path of the file to be decrypt'\n") 114 | os.Exit(conf.Exit_CmdLineParaErr) 115 | } 116 | client.FileDecrypt(args[0]) 117 | } 118 | -------------------------------------------------------------------------------- /command/global_flags.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "cess-portal/conf" 5 | "cess-portal/internal/logger" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | type GlobalFlags struct { 10 | ConfFilePath string 11 | } 12 | 13 | func InitComponents(cmd *cobra.Command) { 14 | configpath1, _ := cmd.Flags().GetString("config") 15 | configpath2, _ := cmd.Flags().GetString("c") 16 | if configpath1 != "" { 17 | conf.ConfFilePath = configpath1 18 | } else { 19 | conf.ConfFilePath = configpath2 20 | } 21 | conf.InitConf() 22 | logger.InitLogger() 23 | } 24 | -------------------------------------------------------------------------------- /command/purchase_command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "cess-portal/internal/logger" 7 | "fmt" 8 | "github.com/spf13/cobra" 9 | "os" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | func NewPurchaseCommand() *cobra.Command { 15 | tc := &cobra.Command{ 16 | Use: "purchase ", 17 | Short: "Purchase commands use for implement all of related transaction function", 18 | } 19 | 20 | tc.AddCommand(NewPurchaseBuySpaceCommand()) 21 | tc.AddCommand(NewPurchaseObtainCommand()) 22 | 23 | return tc 24 | } 25 | 26 | func NewPurchaseBuySpaceCommand() *cobra.Command { 27 | tbs := &cobra.Command{ 28 | Use: "storage ", 29 | Short: "Buy CESS storage space", 30 | Long: ` storage space quantity you want to buy,unit:GB; storage space you want to rental, unit:Month; set the expected price(integer),unit(TCESS/GB) for the purchase, if input null mean accept the CESS real-time storage unit price.`, 31 | 32 | Run: PurchaseBuySpaceCommandFunc, 33 | } 34 | 35 | return tbs 36 | } 37 | 38 | func PurchaseBuySpaceCommandFunc(cmd *cobra.Command, args []string) { 39 | InitComponents(cmd) 40 | var expected = 0 41 | var quantity = 0 42 | var duration = 0 43 | var err error 44 | if len(args) < 2 { 45 | fmt.Printf("[Error]Please fill in the amount of storage space you want to purchase! Usage: cessctl purchase storage \n") 46 | logger.OutPutLogger.Sugar().Infof("[Error]Please fill in the amount of storage space you want to purchase! Usage: cessctl purchase storage \n") 47 | os.Exit(conf.Exit_CmdLineParaErr) 48 | } 49 | if len(args) > 2 { 50 | expected, err = strconv.Atoi(args[2]) 51 | if err != nil || expected < 0 { 52 | fmt.Printf("[Error]Please enter the correct number (integer) in \n") 53 | logger.OutPutLogger.Sugar().Infof("[Error]Please enter the correct number (integer) in or \n") 54 | os.Exit(conf.Exit_CmdLineParaErr) 55 | } 56 | } 57 | quantity, err1 := strconv.Atoi(args[0]) 58 | duration, err2 := strconv.Atoi(args[1]) 59 | if err1 != nil || err2 != nil || quantity < 0 { 60 | fmt.Printf("[Error]Please enter the correct number (integer) in or \n") 61 | logger.OutPutLogger.Sugar().Infof("[Error]Please enter the correct number (integer) in \n") 62 | os.Exit(conf.Exit_CmdLineParaErr) 63 | } 64 | 65 | client.Expansion(quantity, duration, expected) 66 | } 67 | 68 | func NewPurchaseObtainCommand() *cobra.Command { 69 | tbs := &cobra.Command{ 70 | Use: "free
", 71 | Short: "Top up free TCESS from the faucet", 72 | Long: `Free command use for obtain the TCESS tokens from the CESS faucet service, the amount of each top up is 10000 TCESS`, 73 | 74 | Run: PurchaseObtainCommandFunc, 75 | } 76 | 77 | return tbs 78 | } 79 | 80 | func PurchaseObtainCommandFunc(cmd *cobra.Command, args []string) { 81 | InitComponents(cmd) 82 | if len(args) == 0 { 83 | fmt.Printf("[Error]Please fill in the account public key! Usage: cessctl trade obtain ") 84 | os.Exit(conf.Exit_CmdLineParaErr) 85 | } 86 | if len(args[0]) != 49 { 87 | fmt.Printf("[Error]Please enter the correct number of digits for the wallet address!\n") 88 | os.Exit(conf.Exit_CmdLineParaErr) 89 | } 90 | if !strings.HasPrefix(args[0], "c") { 91 | fmt.Println("[Error]The wallet address you entered is not in the correct format!\n") 92 | os.Exit(conf.Exit_CmdLineParaErr) 93 | } 94 | 95 | client.ObtainFromFaucet(args[0]) 96 | } 97 | -------------------------------------------------------------------------------- /command/query_command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "cess-portal/client" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | func NewQueryCommand() *cobra.Command { 9 | fc := &cobra.Command{ 10 | Use: "query ", 11 | Short: "Query commands use for implement all of related find specific detail information", 12 | } 13 | 14 | fc.AddCommand(NewQueryPriceCommand()) 15 | fc.AddCommand(NewQueryPurchasedSpaceCommand()) 16 | fc.AddCommand(NewQueryFileCommand()) 17 | 18 | return fc 19 | } 20 | 21 | // NewFindPriceCommand 22 | func NewQueryPriceCommand() *cobra.Command { 23 | cc := &cobra.Command{ 24 | Use: "price", 25 | Short: "Query the current storage space price(TCESS/GB)", 26 | Long: `Price command use for query and display the CESS network real-time storage space unit price (unit: TCESS/G).`, 27 | 28 | Run: QueryPriceCommandFunc, 29 | } 30 | 31 | return cc 32 | } 33 | 34 | func QueryPriceCommandFunc(cmd *cobra.Command, args []string) { 35 | InitComponents(cmd) 36 | 37 | client.QueryPrice() 38 | } 39 | 40 | func NewQueryPurchasedSpaceCommand() *cobra.Command { 41 | cc := &cobra.Command{ 42 | Use: "space", 43 | Short: "Query real-time storage space detailed information", 44 | Long: `Space command use for query and display current account purchased storage space usage (used and remaining).. 45 | `, 46 | 47 | Run: QueryPurchasedSpaceCommand, 48 | } 49 | 50 | return cc 51 | } 52 | 53 | func QueryPurchasedSpaceCommand(cmd *cobra.Command, args []string) { 54 | InitComponents(cmd) 55 | 56 | client.QueryPurchasedSpace() 57 | } 58 | 59 | func NewQueryFileCommand() *cobra.Command { 60 | cc := &cobra.Command{ 61 | Use: "file ", 62 | Short: "Query the uploaded files information", 63 | Long: `File command use for query the CESS chain uploaded file information, if you choose do not input in the then show the all uploaded file list information. `, 64 | 65 | Run: QueryFileCommand, 66 | } 67 | 68 | return cc 69 | } 70 | 71 | func QueryFileCommand(cmd *cobra.Command, args []string) { 72 | InitComponents(cmd) 73 | fileid := "" 74 | if len(args) != 0 { 75 | fileid = args[0] 76 | } else { 77 | cmd.Println("No parameter query, return a list of all files") 78 | } 79 | 80 | client.QueryFile(fileid) 81 | } 82 | -------------------------------------------------------------------------------- /conf/conf_file.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "cess-portal/tools" 5 | "fmt" 6 | "gopkg.in/yaml.v3" 7 | "io/ioutil" 8 | "os" 9 | ) 10 | 11 | type CessClient struct { 12 | BoardInfo BoardInfo `yaml:"boardInfo"` 13 | ChainData ChainData `yaml:"chainData"` 14 | PathInfo PathInfo `yaml:"pathInfo"` 15 | } 16 | 17 | type BoardInfo struct { 18 | BoardPath string `yaml:"boardPath"` 19 | } 20 | type ChainData struct { 21 | CessRpcAddr string `yaml:"cessRpcAddr"` 22 | FaucetAddress string `yaml:"faucetAddress"` 23 | IdAccountPhraseOrSeed string `yaml:"idAccountPhraseOrSeed"` 24 | AccountPublicKey string `yaml:"accountPublicKey"` 25 | WalletAddress string `yaml:"walletAddress"` 26 | } 27 | type PathInfo struct { 28 | KeyPath string `yaml:"keyPath"` 29 | InstallPath string `yaml:"installPath"` 30 | } 31 | 32 | var ClientConf = new(CessClient) 33 | var ConfFilePath string 34 | 35 | func InitConf() { 36 | if ConfFilePath == "" { 37 | ConfFilePath = Conf_File_Path_D 38 | } 39 | _, err := os.Stat(ConfFilePath) 40 | if err != nil { 41 | fmt.Printf("\x1b[%dm[err]\x1b[0m The '%v' config file does not exist\n", 41, ConfFilePath) 42 | os.Exit(Exit_CmdLineParaErr) 43 | } 44 | yamlFile, err := ioutil.ReadFile(ConfFilePath) 45 | if err != nil { 46 | fmt.Printf("\x1b[%dm[err]\x1b[0m The '%v' file read error\n", 41, ConfFilePath) 47 | os.Exit(Exit_ConfErr) 48 | } 49 | err = yaml.Unmarshal(yamlFile, ClientConf) 50 | if err != nil { 51 | fmt.Printf("\x1b[%dm[err]\x1b[0m The '%v' file format error\n", 41, ConfFilePath) 52 | os.Exit(Exit_ConfErr) 53 | } 54 | pubkey, err := tools.DecodeToPub(ClientConf.ChainData.WalletAddress, tools.ChainCessTestPrefix) 55 | if err != nil { 56 | fmt.Printf("[Error]The wallet address you entered is incorrect, please re-enter\n") 57 | os.Exit(Exit_ConfErr) 58 | } 59 | ClientConf.ChainData.AccountPublicKey = tools.PubBytesTo0XString(pubkey) 60 | } 61 | -------------------------------------------------------------------------------- /conf/default.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | //default set up about cess client 4 | var ( 5 | Conf_File_Path_D = "/etc/cess.d/cess_client.yaml" 6 | Board_Path_D = "/etc/cess.d/" 7 | ) 8 | 9 | /* 10 | system set up 11 | */ 12 | const ( 13 | Exit_Normal = 0 14 | Exit_CmdLineParaErr = -1 15 | Exit_ConfErr = -2 16 | Exit_ChainErr = -3 17 | Exit_SystemErr = -4 18 | ) 19 | 20 | const MaxBackups = 6 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module cess-portal 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/btcsuite/btcutil v1.0.2 7 | github.com/bwmarrin/snowflake v0.3.0 8 | github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.0 9 | github.com/deckarep/golang-set v1.8.0 10 | github.com/ethereum/go-ethereum v1.10.17 11 | github.com/golang/protobuf v1.5.2 12 | github.com/gorilla/websocket v1.4.2 13 | github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef 14 | github.com/natefinch/lumberjack v2.0.0+incompatible 15 | github.com/pkg/errors v0.9.1 16 | github.com/spf13/cobra v1.3.0 17 | go.uber.org/zap v1.17.0 18 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 19 | google.golang.org/protobuf v1.27.1 20 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 21 | ) 22 | 23 | require ( 24 | github.com/ChainSafe/go-schnorrkel v0.0.0-20210318173838-ccb5cd955283 // indirect 25 | github.com/cosmos/go-bip39 v1.0.0 // indirect 26 | github.com/decred/base58 v1.0.3 // indirect 27 | github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect 28 | github.com/go-stack/stack v1.8.0 // indirect 29 | github.com/gtank/merlin v0.1.1 // indirect 30 | github.com/gtank/ristretto255 v0.1.2 // indirect 31 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 32 | github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect 33 | github.com/pierrec/xxHash v0.1.5 // indirect 34 | github.com/rs/cors v1.8.0 // indirect 35 | github.com/spf13/pflag v1.0.5 // indirect 36 | github.com/vedhavyas/go-subkey v1.0.2 // indirect 37 | go.uber.org/atomic v1.7.0 // indirect 38 | go.uber.org/multierr v1.6.0 // indirect 39 | golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect 40 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect 41 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect 42 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /install_ctl/build.bat: -------------------------------------------------------------------------------- 1 | SET CGO_ENABLED=0 2 | SET GOOS=linux 3 | SET GOARCH=amd64 4 | go mod tidy 5 | go build -o cessctl ../cessctl/main.go -------------------------------------------------------------------------------- /install_ctl/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | go mod tidy 3 | go build -o cessctl ../cessctl/main.go -------------------------------------------------------------------------------- /install_ctl/cess_client.yaml: -------------------------------------------------------------------------------- 1 | boardInfo: 2 | boardPath : "" 3 | 4 | chainData: 5 | cessRpcAddr : "" 6 | faucetAddress : "" 7 | idAccountPhraseOrSeed : "" 8 | walletAddress : "" 9 | 10 | pathInfo: 11 | keyPath : "" 12 | installPath : "" -------------------------------------------------------------------------------- /install_ctl/install-cessctl.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | #-----------------------------------------------------------------------------# 4 | # Modify the following configuration items according to your actual situation # 5 | #↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓# 6 | ##Log information output directory 7 | boardPath='' 8 | ##Cess chain communication address 9 | cessRpcAddr='' 10 | ##Faucet address 11 | faucetAddress='' 12 | ##Wallet private key 13 | idAccountPhraseOrSeed='' 14 | ##Wallet public key 15 | walletAddress='' 16 | ##The storage location of the file encryption password entered when uploading the file,using an absolute address 17 | keyPath='' 18 | ##The path to download the file,using an absolute address 19 | installPath='' 20 | 21 | if [ -f "/usr/bin/cessctl" ]; then 22 | rm -rf /usr/bin/cessctl 23 | fi 24 | if [ -e "/etc/cess.d/" ]; then 25 | rm -rf /etc/cess.d/ 26 | fi 27 | mkdir /etc/cess.d/ 28 | cp ./cess_client.yaml /etc/cess.d/ 29 | chmod 777 ./cessctl 30 | mv ./cessctl /usr/bin/ 31 | cessctl -h 32 | 33 | sudo sed -i "s|boardPath : \"\"|boardPath : \"${boardPath}\"|g" /etc/cess.d/cess_client.yaml 34 | sudo sed -i "s|cessRpcAddr : \"\"|cessRpcAddr : \"${cessRpcAddr}\"|g" /etc/cess.d/cess_client.yaml 35 | sudo sed -i "s|faucetAddress : \"\"|faucetAddress : \"${faucetAddress}\"|g" /etc/cess.d/cess_client.yaml 36 | sudo sed -i "s|idAccountPhraseOrSeed : \"\"|idAccountPhraseOrSeed : \"${idAccountPhraseOrSeed}\"|g" /etc/cess.d/cess_client.yaml 37 | sudo sed -i "s|walletAddress : \"\"|walletAddress : \"${walletAddress}\"|g" /etc/cess.d/cess_client.yaml 38 | sudo sed -i "s|keyPath : \"\"|keyPath : \"${keyPath}\"|g" /etc/cess.d/cess_client.yaml 39 | sudo sed -i "s|installPath : \"\"|installPath : \"${installPath}\"|g" /etc/cess.d/cess_client.yaml 40 | -------------------------------------------------------------------------------- /internal/chain/chainstate.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import ( 4 | "cess-portal/conf" 5 | "fmt" 6 | "github.com/centrifuge/go-substrate-rpc-client/v4/types" 7 | "github.com/pkg/errors" 8 | "strconv" 9 | ) 10 | 11 | //UserHoldSpaceDetails means to get specific information about user space 12 | func (ci *CessInfo) UserHoldSpaceDetails() (UserHoldSpaceDetails, error) { 13 | var ( 14 | err error 15 | data UserHoldSpaceDetails 16 | ) 17 | api.getSubstrateApiSafe() 18 | defer func() { 19 | api.releaseSubstrateApi() 20 | err := recover() 21 | if err != nil { 22 | fmt.Printf("[Error]Recover UserHoldSpaceDetails panic fail :%s\n", err) 23 | } 24 | }() 25 | meta, err := api.r.RPC.State.GetMetadataLatest() 26 | if err != nil { 27 | return data, errors.Wrapf(err, "[%v.%v:GetMetadataLatest]", ci.ChainModule, ci.ChainModuleMethod) 28 | } 29 | 30 | publickey, err := types.NewMultiAddressFromHexAccountID(conf.ClientConf.ChainData.AccountPublicKey) 31 | if err != nil { 32 | return data, err 33 | } 34 | key, err := types.CreateStorageKey(meta, ci.ChainModule, ci.ChainModuleMethod, publickey.AsID[:]) 35 | if err != nil { 36 | return data, errors.Wrapf(err, "[%v.%v:CreateStorageKey]", ci.ChainModule, ci.ChainModuleMethod) 37 | } 38 | 39 | _, err = api.r.RPC.State.GetStorageLatest(key, &data) 40 | if err != nil { 41 | return data, errors.Wrapf(err, "[%v.%v:GetStorageLatest]", ci.ChainModule, ci.ChainModuleMethod) 42 | } 43 | return data, nil 44 | } 45 | 46 | func (userinfo UserHoldSpaceDetails) String() string { 47 | PurchasedSpace, _ := strconv.Atoi(userinfo.PurchasedSpace.String()) 48 | UsedSpace, _ := strconv.Atoi(userinfo.UsedSpace.String()) 49 | RemainingSpace, _ := strconv.Atoi(userinfo.RemainingSpace.String()) 50 | if UsedSpace/1024/1024 == 0 && UsedSpace != 0 { 51 | UsedSpace = 1 52 | } else { 53 | UsedSpace = UsedSpace / 1024 / 1024 54 | } 55 | ret := "———————————————————You Purchased Space———————————————————\n" 56 | ret += " PurchasedSpace:" + strconv.Itoa(PurchasedSpace/1024/1024) + "(MB)\n" 57 | ret += " UsedSpace:" + strconv.Itoa(UsedSpace) + "(MB)\n" 58 | ret += " RemainingSpace:" + strconv.Itoa(RemainingSpace/1024/1024) + "(MB)\n" 59 | ret += "—————————————————————————————————————————————————————————" 60 | return ret 61 | } 62 | 63 | //GetPrice means the size of the space purchased by all customers of the whole CESS system 64 | func (ci *CessInfo) GetPrice() (types.U128, error) { 65 | var ( 66 | err error 67 | data types.U128 68 | ) 69 | api.getSubstrateApiSafe() 70 | defer func() { 71 | api.releaseSubstrateApi() 72 | err := recover() 73 | if err != nil { 74 | fmt.Printf("[Error]Recover UserHoldSpaceDetails panic :%s\n", err) 75 | } 76 | }() 77 | meta, err := api.r.RPC.State.GetMetadataLatest() 78 | if err != nil { 79 | return data, errors.Wrapf(err, "[%v.%v:GetMetadataLatest]", ci.ChainModule, ci.ChainModuleMethod) 80 | } 81 | 82 | key, err := types.CreateStorageKey(meta, ci.ChainModule, ci.ChainModuleMethod) 83 | if err != nil { 84 | return data, errors.Wrapf(err, "[%v.%v:CreateStorageKey]", ci.ChainModule, ci.ChainModuleMethod) 85 | } 86 | 87 | _, err = api.r.RPC.State.GetStorageLatest(key, &data) 88 | if err != nil { 89 | return data, errors.Wrapf(err, "[%v.%v:GetStorageLatest]", ci.ChainModule, ci.ChainModuleMethod) 90 | } 91 | return data, nil 92 | } 93 | 94 | //GetFileInfo means to get the specific information of the file through the current fileid 95 | func (ci *CessInfo) GetFileInfo(fileid string) (FileInfo, error) { 96 | var ( 97 | err error 98 | data FileInfo 99 | ) 100 | 101 | api.getSubstrateApiSafe() 102 | defer func() { 103 | api.releaseSubstrateApi() 104 | err := recover() 105 | if err != nil { 106 | fmt.Printf("[Error]Recover UserHoldSpaceDetails panic fail :%s\n", err) 107 | } 108 | }() 109 | meta, err := api.r.RPC.State.GetMetadataLatest() 110 | if err != nil { 111 | return data, errors.Wrapf(err, "[%v.%v:GetMetadataLatest]", ci.ChainModule, ci.ChainModuleMethod) 112 | } 113 | id, err := types.EncodeToBytes(fileid) 114 | 115 | key, err := types.CreateStorageKey(meta, ci.ChainModule, ci.ChainModuleMethod, types.NewBytes(id)) 116 | if err != nil { 117 | return data, errors.Wrapf(err, "[%v.%v:CreateStorageKey]", ci.ChainModule, ci.ChainModuleMethod) 118 | } 119 | 120 | _, err = api.r.RPC.State.GetStorageLatest(key, &data) 121 | if err != nil { 122 | return data, errors.Wrapf(err, "[%v.%v:GetStorageLatest]", ci.ChainModule, ci.ChainModuleMethod) 123 | } 124 | return data, nil 125 | } 126 | 127 | //GetFileList means to get a list of all files of the current user 128 | func (ci *CessInfo) GetFileList() ([][]byte, error) { 129 | var ( 130 | err error 131 | data = make([][]byte, 0) 132 | ) 133 | api.getSubstrateApiSafe() 134 | defer func() { 135 | api.releaseSubstrateApi() 136 | err := recover() 137 | if err != nil { 138 | fmt.Printf("[Error]Recover UserHoldSpaceDetails panic fail :%s\n", err) 139 | } 140 | }() 141 | meta, err := api.r.RPC.State.GetMetadataLatest() 142 | if err != nil { 143 | return data, errors.Wrapf(err, "[%v.%v:GetMetadataLatest]", ci.ChainModule, ci.ChainModuleMethod) 144 | } 145 | publickey, err := types.NewMultiAddressFromHexAccountID(conf.ClientConf.ChainData.AccountPublicKey) 146 | if err != nil { 147 | return data, err 148 | } 149 | 150 | key, err := types.CreateStorageKey(meta, ci.ChainModule, ci.ChainModuleMethod, publickey.AsID[:]) 151 | if err != nil { 152 | return data, errors.Wrapf(err, "[%v.%v:CreateStorageKey]", ci.ChainModule, ci.ChainModuleMethod) 153 | } 154 | 155 | _, err = api.r.RPC.State.GetStorageLatest(key, &data) 156 | if err != nil { 157 | return data, errors.Wrapf(err, "[%v.%v:GetStorageLatest]", ci.ChainModule, ci.ChainModuleMethod) 158 | } 159 | return data, nil 160 | } 161 | 162 | func (fileinfo FileInfo) String() string { 163 | ret := "———————————————————File Information———————————————————\n" 164 | ret += fmt.Sprintf(" Filename:%v\n", string(fileinfo.File_Name[:])) 165 | ret += fmt.Sprintf(" Public:%v\n", fileinfo.Public) 166 | ret += fmt.Sprintf(" Filehash:%v\n", string(fileinfo.FileHash[:])) 167 | ret += fmt.Sprintf(" Backups:%v\n", fileinfo.Backups) 168 | ret += fmt.Sprintf(" Filesize:%v(KB)\n", fileinfo.FileSize/types.NewU64(1024)) 169 | ret += fmt.Sprintf(" Downloadfee:%v(TCESS)\n", fileinfo.Downloadfee) 170 | return ret 171 | } 172 | 173 | //GetSchedulerInfo means to get all currently registered schedulers 174 | func (ci *CessInfo) GetSchedulerInfo() ([]SchedulerInfo, error) { 175 | var ( 176 | err error 177 | data []SchedulerInfo 178 | ) 179 | api.getSubstrateApiSafe() 180 | defer func() { 181 | api.releaseSubstrateApi() 182 | err := recover() 183 | if err != nil { 184 | fmt.Printf("[Error]Recover UserHoldSpaceDetails panic fail :%s\n", err) 185 | } 186 | }() 187 | meta, err := api.r.RPC.State.GetMetadataLatest() 188 | if err != nil { 189 | return data, errors.Wrapf(err, "[%v.%v:GetMetadataLatest]", ci.ChainModule, ci.ChainModuleMethod) 190 | } 191 | 192 | //publickey, err := types.NewMultiAddressFromHexAccountID(conf.ClientConf.ChainData.AccountPublicKey) 193 | //if err != nil { 194 | // return data, err 195 | //} 196 | key, err := types.CreateStorageKey(meta, ci.ChainModule, ci.ChainModuleMethod) 197 | if err != nil { 198 | return data, errors.Wrapf(err, "[%v.%v:CreateStorageKey]", ci.ChainModule, ci.ChainModuleMethod) 199 | } 200 | 201 | _, err = api.r.RPC.State.GetStorageLatest(key, &data) 202 | if err != nil { 203 | return data, errors.Wrapf(err, "[%v.%v:GetStorageLatest]", ci.ChainModule, ci.ChainModuleMethod) 204 | } 205 | return data, nil 206 | } 207 | -------------------------------------------------------------------------------- /internal/chain/events.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import "github.com/centrifuge/go-substrate-rpc-client/v4/types" 4 | 5 | type Event_PPBNoOnTimeSubmit struct { 6 | Phase types.Phase 7 | Acc types.AccountID 8 | SegmentId types.U64 9 | Topics []types.Hash 10 | } 11 | 12 | type Event_PPDNoOnTimeSubmit struct { 13 | Phase types.Phase 14 | Acc types.AccountID 15 | SegmentId types.U64 16 | Topics []types.Hash 17 | } 18 | 19 | type Event_ChallengeProof struct { 20 | Phase types.Phase 21 | PeerId types.U64 22 | Fileid types.Bytes 23 | Topics []types.Hash 24 | } 25 | 26 | type Event_VerifyProof struct { 27 | Phase types.Phase 28 | PeerId types.U64 29 | Fileid types.Bytes 30 | Topics []types.Hash 31 | } 32 | 33 | //------------------------Sminer--------------------------------- 34 | type Event_Registered struct { 35 | Phase types.Phase 36 | Acc types.AccountID 37 | StakingVal types.U128 38 | Topics []types.Hash 39 | } 40 | 41 | type Event_TimedTask struct { 42 | Phase types.Phase 43 | Topics []types.Hash 44 | } 45 | 46 | type Event_DrawFaucetMoney struct { 47 | Phase types.Phase 48 | Topics []types.Hash 49 | } 50 | 51 | type Event_FaucetTopUpMoney struct { 52 | Phase types.Phase 53 | Acc types.AccountID 54 | Topics []types.Hash 55 | } 56 | 57 | type Event_LessThan24Hours struct { 58 | Phase types.Phase 59 | Last types.U32 60 | Now types.U32 61 | Topics []types.Hash 62 | } 63 | type Event_AlreadyFrozen struct { 64 | Phase types.Phase 65 | Acc types.AccountID 66 | Topics []types.Hash 67 | } 68 | 69 | type Event_MinerExit struct { 70 | Phase types.Phase 71 | Acc types.AccountID 72 | Topics []types.Hash 73 | } 74 | 75 | type Event_MinerClaim struct { 76 | Phase types.Phase 77 | Acc types.AccountID 78 | Topics []types.Hash 79 | } 80 | 81 | type Event_IncreaseCollateral struct { 82 | Phase types.Phase 83 | Acc types.AccountID 84 | Balance types.U128 85 | Topics []types.Hash 86 | } 87 | 88 | type Event_Deposit struct { 89 | Phase types.Phase 90 | Balance types.U128 91 | Topics []types.Hash 92 | } 93 | 94 | //------------------------FileBank------------------------------- 95 | type Event_DeleteFile struct { 96 | Phase types.Phase 97 | Acc types.AccountID 98 | Fileid types.Bytes 99 | Topics []types.Hash 100 | } 101 | 102 | type Event_BuySpace struct { 103 | Phase types.Phase 104 | Acc types.AccountID 105 | Size types.U128 106 | Fee types.U128 107 | Topics []types.Hash 108 | } 109 | 110 | type Event_FileUpload struct { 111 | Phase types.Phase 112 | Acc types.AccountID 113 | Topics []types.Hash 114 | } 115 | 116 | type Event_FileUpdate struct { 117 | Phase types.Phase 118 | Acc types.AccountID 119 | Fileid types.Bytes 120 | Topics []types.Hash 121 | } 122 | 123 | type Event_LeaseExpireIn24Hours struct { 124 | Phase types.Phase 125 | Acc types.AccountID 126 | Size types.U128 127 | Topics []types.Hash 128 | } 129 | 130 | type Event_FileChangeState struct { 131 | Phase types.Phase 132 | Acc types.AccountID 133 | Fileid types.Bytes 134 | Topics []types.Hash 135 | } 136 | 137 | type Event_BuyFile struct { 138 | Phase types.Phase 139 | Acc types.AccountID 140 | Money types.U128 141 | Fileid types.Bytes 142 | Topics []types.Hash 143 | } 144 | 145 | type Event_Purchased struct { 146 | Phase types.Phase 147 | Acc types.AccountID 148 | Fileid types.Bytes 149 | Topics []types.Hash 150 | } 151 | 152 | type Event_InsertFileSlice struct { 153 | Phase types.Phase 154 | Fileid types.Bytes 155 | Topics []types.Hash 156 | } 157 | 158 | type Event_LeaseExpired struct { 159 | Phase types.Phase 160 | Acc types.AccountID 161 | Size types.U128 162 | Topics []types.Hash 163 | } 164 | 165 | type Event_FillerUpload struct { 166 | Phase types.Phase 167 | Acc types.AccountID 168 | Filesize types.U64 169 | Topics []types.Hash 170 | } 171 | 172 | type Event_ClearInvalidFile struct { 173 | Phase types.Phase 174 | Acc types.AccountID 175 | Fileid types.Bytes 176 | Topics []types.Hash 177 | } 178 | 179 | type Event_RecoverFile struct { 180 | Phase types.Phase 181 | Acc types.AccountID 182 | Fileid types.Bytes 183 | Topics []types.Hash 184 | } 185 | 186 | //------------------------FileMap-------------------------------- 187 | type Event_RegistrationScheduler struct { 188 | Phase types.Phase 189 | Acc types.AccountID 190 | Ip types.Bytes 191 | Topics []types.Hash 192 | } 193 | 194 | //------------------------other system--------------------------- 195 | type Event_UnsignedPhaseStarted struct { 196 | Phase types.Phase 197 | Round types.U32 198 | Topics []types.Hash 199 | } 200 | 201 | type Event_SignedPhaseStarted struct { 202 | Phase types.Phase 203 | Round types.U32 204 | Topics []types.Hash 205 | } 206 | 207 | type Event_SolutionStored struct { 208 | Phase types.Phase 209 | Election_compute types.ElectionCompute 210 | Prev_ejected types.Bool 211 | Topics []types.Hash 212 | } 213 | 214 | type Event_Balances_Withdraw struct { 215 | Phase types.Phase 216 | Who types.AccountID 217 | Amount types.U128 218 | Topics []types.Hash 219 | } 220 | type Event_OutstandingChallenges struct { 221 | Phase types.Phase 222 | PeerId types.U64 223 | Fileid types.Bytes 224 | Topics []types.Hash 225 | } 226 | type Event_ReceiveSpace struct { 227 | Phase types.Phase 228 | Acc types.AccountID 229 | Topics []types.Hash 230 | } 231 | 232 | // All event types 233 | type MyEventRecords struct { 234 | //system 235 | types.EventRecords 236 | //SegmentBook 237 | SegmentBook_PPBNoOnTimeSubmit []Event_PPBNoOnTimeSubmit 238 | SegmentBook_PPDNoOnTimeSubmit []Event_PPDNoOnTimeSubmit 239 | SegmentBook_ChallengeProof []Event_ChallengeProof 240 | SegmentBook_VerifyProof []Event_VerifyProof 241 | SegmentBook_OutstandingChallenges []Event_OutstandingChallenges 242 | //Sminer 243 | Sminer_Registered []Event_Registered 244 | Sminer_TimedTask []Event_TimedTask 245 | Sminer_DrawFaucetMoney []Event_DrawFaucetMoney 246 | Sminer_FaucetTopUpMoney []Event_FaucetTopUpMoney 247 | Sminer_LessThan24Hours []Event_LessThan24Hours 248 | Sminer_AlreadyFrozen []Event_AlreadyFrozen 249 | Sminer_MinerExit []Event_MinerExit 250 | Sminer_MinerClaim []Event_MinerClaim 251 | Sminer_IncreaseCollateral []Event_IncreaseCollateral 252 | Sminer_Deposit []Event_Deposit 253 | //FileBank 254 | FileBank_DeleteFile []Event_DeleteFile 255 | FileBank_BuySpace []Event_BuySpace 256 | FileBank_FileUpload []Event_FileUpload 257 | FileBank_FileUpdate []Event_FileUpdate 258 | FileBank_LeaseExpireIn24Hours []Event_LeaseExpireIn24Hours 259 | FileBank_FileChangeState []Event_FileChangeState 260 | FileBank_BuyFile []Event_BuyFile 261 | FileBank_Purchased []Event_Purchased 262 | FileBank_InsertFileSlice []Event_InsertFileSlice 263 | FileBank_LeaseExpired []Event_LeaseExpired 264 | FileBank_FillerUpload []Event_FillerUpload 265 | FileBank_ClearInvalidFile []Event_ClearInvalidFile 266 | FileBank_RecoverFile []Event_RecoverFile 267 | FileBank_ReceiveSpace []Event_ReceiveSpace 268 | //FileMap 269 | FileMap_RegistrationScheduler []Event_RegistrationScheduler 270 | //other system 271 | ElectionProviderMultiPhase_UnsignedPhaseStarted []Event_UnsignedPhaseStarted 272 | ElectionProviderMultiPhase_SignedPhaseStarted []Event_SignedPhaseStarted 273 | ElectionProviderMultiPhase_SolutionStored []Event_SolutionStored 274 | Balances_Withdraw []Event_Balances_Withdraw 275 | } 276 | -------------------------------------------------------------------------------- /internal/chain/main.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import ( 4 | "cess-portal/conf" 5 | "fmt" 6 | gsrpc "github.com/centrifuge/go-substrate-rpc-client/v4" 7 | "os" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type mySubstrateApi struct { 13 | wlock sync.Mutex 14 | r *gsrpc.SubstrateAPI 15 | } 16 | 17 | var api mySubstrateApi 18 | 19 | //Initialize the chain connection handle 20 | func Chain_Init() { 21 | var err error 22 | 23 | api.r, err = gsrpc.NewSubstrateAPI(conf.ClientConf.ChainData.CessRpcAddr) 24 | if err != nil { 25 | fmt.Printf("[Error]Problem with chain rpc:%s\n", err) 26 | os.Exit(conf.Exit_ChainErr) 27 | } 28 | go substrateAPIKeepAlive() 29 | } 30 | 31 | func substrateAPIKeepAlive() { 32 | var ( 33 | err error 34 | count_r uint8 = 0 35 | peer uint64 = 0 36 | ) 37 | 38 | for range time.Tick(time.Second * 25) { 39 | if count_r <= 1 { 40 | peer, err = healthchek(api.r) 41 | if err != nil || peer == 0 { 42 | count_r++ 43 | } 44 | } 45 | if count_r > 1 { 46 | count_r = 2 47 | api.r, err = gsrpc.NewSubstrateAPI(conf.ClientConf.ChainData.CessRpcAddr) 48 | if err != nil { 49 | 50 | } else { 51 | count_r = 0 52 | } 53 | } 54 | } 55 | } 56 | 57 | func healthchek(a *gsrpc.SubstrateAPI) (uint64, error) { 58 | defer func() { 59 | err := recover() 60 | if err != nil { 61 | fmt.Printf("[Error]Recover healthchek panic fail :", err) 62 | } 63 | }() 64 | h, err := a.RPC.System.Health() 65 | return uint64(h.Peers), err 66 | } 67 | 68 | func (myapi *mySubstrateApi) getSubstrateApiSafe() { 69 | myapi.wlock.Lock() 70 | return 71 | } 72 | 73 | func (myapi *mySubstrateApi) releaseSubstrateApi() { 74 | myapi.wlock.Unlock() 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /internal/chain/transPram.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import "github.com/centrifuge/go-substrate-rpc-client/v4/types" 4 | 5 | var ( 6 | //trade 7 | BuySpaceTransactionName = "FileBank.buy_space" 8 | UploadFileTransactionName = "FileBank.upload" 9 | DeleteFileTransactionName = "FileBank.delete_file" 10 | 11 | //find 12 | PurchasedSpaceChainModule = "FileBank" 13 | PurchasedSpaceModuleMethod = "UserHoldSpaceDetails" 14 | 15 | FindPriceChainModule = "FileBank" 16 | FindPriceModuleMethod = "UnitPrice" 17 | 18 | FindFileChainModule = "FileBank" 19 | FindFileModuleMethod = []string{"File", "UserHoldFileList"} 20 | 21 | FindSchedulerInfoModule = "FileMap" 22 | FindSchedulerInfoMethod = "SchedulerMap" 23 | ) 24 | 25 | type CessInfo struct { 26 | RpcAddr string 27 | IdentifyAccountPhrase string 28 | TransactionName string 29 | ChainModule string 30 | ChainModuleMethod string 31 | } 32 | 33 | type UserHoldSpaceDetails struct { 34 | PurchasedSpace types.U128 `json:"purchased_space"` 35 | UsedSpace types.U128 `json:"used_space"` 36 | RemainingSpace types.U128 `json:"remaining_space"` 37 | } 38 | 39 | type FileInfo struct { 40 | //FileId types.Bytes `json:"acc"` //File id 41 | File_Name types.Bytes `json:"file_name"` //File name 42 | FileSize types.U64 `json:"file_size"` //File size 43 | FileHash types.Bytes `json:"file_hash"` //File hash 44 | Public types.Bool `json:"public"` //Public or not 45 | UserAddr types.AccountID `json:"user_addr"` //Upload user's address 46 | FileState types.Bytes `json:"file_state"` //File state 47 | Backups types.U8 `json:"backups"` //Number of backups 48 | Downloadfee types.U128 `json:"downloadfee"` //Download fee 49 | FileDupl []FileDuplicateInfo `json:"file_dupl"` //File backup information list 50 | } 51 | type FileDuplicateInfo struct { 52 | MinerId types.U64 53 | BlockNum types.U32 54 | ScanSize types.U32 55 | Acc types.AccountID 56 | MinerIp types.Bytes 57 | DuplId types.Bytes 58 | RandKey types.Bytes 59 | BlockInfo []BlockInfo 60 | } 61 | type BlockInfo struct { 62 | BlockIndex types.Bytes 63 | BlockSize types.U32 64 | } 65 | 66 | type FileList struct { 67 | Fileid types.Bytes8 `json:"fileid"` 68 | } 69 | type SchedulerInfo struct { 70 | Ip types.Bytes `json:"ip"` 71 | Owner types.AccountID `json:"stash_user"` 72 | ControllerUser types.AccountID `json:"controller_user"` 73 | } 74 | -------------------------------------------------------------------------------- /internal/chain/transaction.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import ( 4 | "cess-portal/conf" 5 | "fmt" 6 | "github.com/centrifuge/go-substrate-rpc-client/v4/signature" 7 | "github.com/centrifuge/go-substrate-rpc-client/v4/types" 8 | "github.com/pkg/errors" 9 | "math/big" 10 | "time" 11 | ) 12 | 13 | //BuySpaceOnChain means initiating a transaction to purchase data on the chain 14 | func (ci *CessInfo) BuySpaceOnChain(Quantity, Duration, Expected int) error { 15 | var ( 16 | err error 17 | accountInfo types.AccountInfo 18 | ) 19 | api.getSubstrateApiSafe() 20 | defer func() { 21 | api.releaseSubstrateApi() 22 | err := recover() 23 | if err != nil { 24 | fmt.Printf("[Error]Recover UserHoldSpaceDetails panic :%s\n", err) 25 | } 26 | }() 27 | keyring, err := signature.KeyringPairFromSecret(ci.IdentifyAccountPhrase, 0) 28 | if err != nil { 29 | return errors.Wrap(err, "KeyringPairFromSecret err") 30 | } 31 | 32 | meta, err := api.r.RPC.State.GetMetadataLatest() 33 | if err != nil { 34 | return errors.Wrap(err, "GetMetadataLatest err") 35 | } 36 | 37 | c, err := types.NewCall(meta, ci.TransactionName, 38 | types.NewU128(*big.NewInt(int64(Quantity))), 39 | types.NewU128(*big.NewInt(int64(Duration))), 40 | types.NewU128(*big.NewInt(int64(Expected)))) 41 | if err != nil { 42 | return errors.Wrap(err, "NewCall err") 43 | } 44 | 45 | ext := types.NewExtrinsic(c) 46 | if err != nil { 47 | return errors.Wrap(err, "NewExtrinsic err") 48 | } 49 | 50 | genesisHash, err := api.r.RPC.Chain.GetBlockHash(0) 51 | if err != nil { 52 | return errors.Wrap(err, "GetBlockHash err") 53 | } 54 | 55 | rv, err := api.r.RPC.State.GetRuntimeVersionLatest() 56 | if err != nil { 57 | return errors.Wrap(err, "GetRuntimeVersionLatest err") 58 | } 59 | 60 | key, err := types.CreateStorageKey(meta, "System", "Account", keyring.PublicKey) 61 | if err != nil { 62 | return errors.Wrap(err, "CreateStorageKey err") 63 | } 64 | 65 | keye, err := types.CreateStorageKey(meta, "System", "Events", nil) 66 | if err != nil { 67 | return errors.Wrap(err, "CreateStorageKey Events err") 68 | } 69 | 70 | ok, err := api.r.RPC.State.GetStorageLatest(key, &accountInfo) 71 | if err != nil { 72 | return errors.Wrap(err, "GetStorageLatest err") 73 | } 74 | if !ok { 75 | return errors.New("GetStorageLatest return value is empty") 76 | } 77 | 78 | o := types.SignatureOptions{ 79 | BlockHash: genesisHash, 80 | Era: types.ExtrinsicEra{IsMortalEra: false}, 81 | GenesisHash: genesisHash, 82 | Nonce: types.NewUCompactFromUInt(uint64(accountInfo.Nonce)), 83 | SpecVersion: rv.SpecVersion, 84 | Tip: types.NewUCompactFromUInt(0), 85 | TransactionVersion: rv.TransactionVersion, 86 | } 87 | 88 | // Sign the transaction 89 | err = ext.Sign(keyring, o) 90 | if err != nil { 91 | return errors.Wrap(err, "Sign err") 92 | } 93 | 94 | // Do the transfer and track the actual status 95 | sub, err := api.r.RPC.Author.SubmitAndWatchExtrinsic(ext) 96 | if err != nil { 97 | return errors.Wrap(err, "SubmitAndWatchExtrinsic err") 98 | } 99 | defer sub.Unsubscribe() 100 | 101 | timeout := time.After(10 * time.Second) 102 | for { 103 | select { 104 | case status := <-sub.Chan(): 105 | if status.IsInBlock { 106 | events := MyEventRecords{} 107 | h, err := api.r.RPC.State.GetStorageRaw(keye, status.AsInBlock) 108 | if err != nil { 109 | return err 110 | } 111 | err = types.EventRecordsRaw(*h).DecodeEventRecords(meta, &events) 112 | if err != nil { 113 | return err 114 | } 115 | if events.FileBank_BuySpace != nil { 116 | return nil 117 | } else { 118 | return errors.New("Buy space on chain fail!") 119 | } 120 | } 121 | case <-timeout: 122 | return errors.Errorf("[%v] tx timeout", ci.TransactionName) 123 | default: 124 | time.Sleep(time.Second) 125 | } 126 | } 127 | } 128 | 129 | //UploadFileMetaInformation means upload file metadata to the chain 130 | func (ci *CessInfo) UploadFileMetaInformation(fileid, filename, filehash string, ispublic bool, backups uint8, filesize uint64, downloadfee *big.Int) (string, error) { 131 | var ( 132 | err error 133 | accountInfo types.AccountInfo 134 | ) 135 | api.getSubstrateApiSafe() 136 | defer func() { 137 | api.releaseSubstrateApi() 138 | err := recover() 139 | if err != nil { 140 | fmt.Printf("[Error]Recover UploadFileMetaInformation panic :%s\n", err) 141 | } 142 | }() 143 | keyring, err := signature.KeyringPairFromSecret(ci.IdentifyAccountPhrase, 0) 144 | if err != nil { 145 | return "", errors.Wrap(err, "KeyringPairFromSecret err") 146 | } 147 | 148 | meta, err := api.r.RPC.State.GetMetadataLatest() 149 | if err != nil { 150 | return "", errors.Wrap(err, "GetMetadataLatest err") 151 | } 152 | 153 | c, err := types.NewCall( 154 | meta, 155 | ci.TransactionName, 156 | types.NewBytes([]byte(conf.ClientConf.ChainData.WalletAddress)), 157 | types.NewBytes([]byte(filename)), 158 | types.NewBytes([]byte(fileid)), 159 | types.NewBytes([]byte(filehash)), 160 | types.NewBool(ispublic), 161 | types.NewU8(backups), 162 | types.NewU64(filesize), 163 | types.NewU128(*downloadfee), 164 | ) 165 | if err != nil { 166 | return "", errors.Wrap(err, "NewCall err") 167 | } 168 | 169 | ext := types.NewExtrinsic(c) 170 | if err != nil { 171 | return "", errors.Wrap(err, "NewExtrinsic err") 172 | } 173 | 174 | genesisHash, err := api.r.RPC.Chain.GetBlockHash(0) 175 | if err != nil { 176 | return "", errors.Wrap(err, "GetBlockHash err") 177 | } 178 | 179 | rv, err := api.r.RPC.State.GetRuntimeVersionLatest() 180 | if err != nil { 181 | return "", errors.Wrap(err, "GetRuntimeVersionLatest err") 182 | } 183 | 184 | key, err := types.CreateStorageKey(meta, "System", "Account", keyring.PublicKey) 185 | if err != nil { 186 | return "", errors.Wrap(err, "CreateStorageKey err") 187 | } 188 | 189 | keye, err := types.CreateStorageKey(meta, "System", "Events", nil) 190 | if err != nil { 191 | return "", errors.Wrap(err, "CreateStorageKey Events err") 192 | } 193 | 194 | ok, err := api.r.RPC.State.GetStorageLatest(key, &accountInfo) 195 | if err != nil { 196 | return "", errors.Wrap(err, "GetStorageLatest err") 197 | } 198 | if !ok { 199 | return "", errors.New("GetStorageLatest return value is empty") 200 | } 201 | 202 | o := types.SignatureOptions{ 203 | BlockHash: genesisHash, 204 | Era: types.ExtrinsicEra{IsMortalEra: false}, 205 | GenesisHash: genesisHash, 206 | Nonce: types.NewUCompactFromUInt(uint64(accountInfo.Nonce)), 207 | SpecVersion: rv.SpecVersion, 208 | Tip: types.NewUCompactFromUInt(0), 209 | TransactionVersion: rv.TransactionVersion, 210 | } 211 | 212 | // Sign the transaction 213 | err = ext.Sign(keyring, o) 214 | if err != nil { 215 | return "", errors.Wrap(err, "Sign err") 216 | } 217 | 218 | // Do the transfer and track the actual status 219 | sub, err := api.r.RPC.Author.SubmitAndWatchExtrinsic(ext) 220 | if err != nil { 221 | return "", errors.Wrap(err, "SubmitAndWatchExtrinsic err") 222 | } 223 | defer sub.Unsubscribe() 224 | 225 | timeout := time.After(10 * time.Second) 226 | for { 227 | select { 228 | case status := <-sub.Chan(): 229 | if status.IsInBlock { 230 | events := MyEventRecords{} 231 | h, err := api.r.RPC.State.GetStorageRaw(keye, status.AsInBlock) 232 | if err != nil { 233 | return "", err 234 | } 235 | err = types.EventRecordsRaw(*h).DecodeEventRecords(meta, &events) 236 | if err != nil { 237 | return "", err 238 | } 239 | if events.FileBank_FileUpload != nil { 240 | return "success!", nil 241 | } else { 242 | return "fail", errors.New("upload file fail") 243 | } 244 | } 245 | case <-timeout: 246 | return "", errors.New("upload file meta info timeout,please check your Internet!") 247 | default: 248 | time.Sleep(time.Second) 249 | } 250 | } 251 | } 252 | 253 | func (ci *CessInfo) DeleteFileOnChain(fileid string) error { 254 | var ( 255 | err error 256 | accountInfo types.AccountInfo 257 | ) 258 | api.getSubstrateApiSafe() 259 | defer func() { 260 | api.releaseSubstrateApi() 261 | err := recover() 262 | if err != nil { 263 | fmt.Printf("[Error]Recover UserHoldSpaceDetails panic :%s\n", err) 264 | } 265 | }() 266 | keyring, err := signature.KeyringPairFromSecret(ci.IdentifyAccountPhrase, 0) 267 | if err != nil { 268 | return errors.Wrap(err, "KeyringPairFromSecret err") 269 | } 270 | 271 | meta, err := api.r.RPC.State.GetMetadataLatest() 272 | if err != nil { 273 | return errors.Wrap(err, "GetMetadataLatest err") 274 | } 275 | 276 | c, err := types.NewCall(meta, ci.TransactionName, types.NewBytes([]byte(fileid))) 277 | if err != nil { 278 | return errors.Wrap(err, "NewCall err") 279 | } 280 | 281 | ext := types.NewExtrinsic(c) 282 | if err != nil { 283 | return errors.Wrap(err, "NewExtrinsic err") 284 | } 285 | 286 | genesisHash, err := api.r.RPC.Chain.GetBlockHash(0) 287 | if err != nil { 288 | return errors.Wrap(err, "GetBlockHash err") 289 | } 290 | 291 | rv, err := api.r.RPC.State.GetRuntimeVersionLatest() 292 | if err != nil { 293 | return errors.Wrap(err, "GetRuntimeVersionLatest err") 294 | } 295 | 296 | key, err := types.CreateStorageKey(meta, "System", "Account", keyring.PublicKey) 297 | if err != nil { 298 | return errors.Wrap(err, "CreateStorageKey Account err") 299 | } 300 | 301 | keye, err := types.CreateStorageKey(meta, "System", "Events", nil) 302 | if err != nil { 303 | return errors.Wrap(err, "CreateStorageKey Events err") 304 | } 305 | 306 | ok, err := api.r.RPC.State.GetStorageLatest(key, &accountInfo) 307 | if err != nil { 308 | return errors.Wrap(err, "GetStorageLatest err") 309 | } 310 | if !ok { 311 | return errors.New("GetStorageLatest return value is empty") 312 | } 313 | 314 | o := types.SignatureOptions{ 315 | BlockHash: genesisHash, 316 | Era: types.ExtrinsicEra{IsMortalEra: false}, 317 | GenesisHash: genesisHash, 318 | Nonce: types.NewUCompactFromUInt(uint64(accountInfo.Nonce)), 319 | SpecVersion: rv.SpecVersion, 320 | Tip: types.NewUCompactFromUInt(0), 321 | TransactionVersion: rv.TransactionVersion, 322 | } 323 | 324 | // Sign the transaction 325 | err = ext.Sign(keyring, o) 326 | if err != nil { 327 | return errors.Wrap(err, "Sign err") 328 | } 329 | 330 | // Do the transfer and track the actual status 331 | sub, err := api.r.RPC.Author.SubmitAndWatchExtrinsic(ext) 332 | if err != nil { 333 | return errors.Wrap(err, "SubmitAndWatchExtrinsic err") 334 | } 335 | defer sub.Unsubscribe() 336 | 337 | timeout := time.After(10 * time.Second) 338 | for { 339 | select { 340 | case status := <-sub.Chan(): 341 | if status.IsInBlock { 342 | events := MyEventRecords{} 343 | h, err := api.r.RPC.State.GetStorageRaw(keye, status.AsInBlock) 344 | if err != nil { 345 | return err 346 | } 347 | err = types.EventRecordsRaw(*h).DecodeEventRecords(meta, &events) 348 | if err != nil { 349 | fmt.Println("+++ DecodeEvent err: ", err) 350 | return err 351 | } 352 | if events.FileBank_DeleteFile != nil { 353 | return nil 354 | } else { 355 | return errors.Errorf("Delete file info on chain fail!") 356 | } 357 | } 358 | case <-timeout: 359 | return errors.Errorf("[%v] tx timeout", ci.TransactionName) 360 | default: 361 | time.Sleep(time.Second) 362 | } 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /internal/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "cess-portal/conf" 5 | "fmt" 6 | "github.com/natefinch/lumberjack" 7 | "go.uber.org/zap" 8 | "go.uber.org/zap/zapcore" 9 | "os" 10 | "path/filepath" 11 | "time" 12 | ) 13 | 14 | var ( 15 | OutPutLogger *zap.Logger 16 | ) 17 | 18 | func InitLogger() { 19 | if len(conf.ClientConf.BoardInfo.BoardPath) == 0 { 20 | conf.ClientConf.BoardInfo.BoardPath = conf.Board_Path_D 21 | } 22 | _, err := os.Stat(conf.ClientConf.BoardInfo.BoardPath) 23 | if err != nil { 24 | err = os.MkdirAll(conf.ClientConf.BoardInfo.BoardPath, os.ModePerm) 25 | if err != nil { 26 | fmt.Printf("\x1b[%dm[err]\x1b[0m Create '%v' file output.log error\n", 41, conf.ClientConf.BoardInfo.BoardPath) 27 | os.Exit(conf.Exit_ConfErr) 28 | } 29 | } 30 | initOutPutLogger() 31 | } 32 | 33 | // output log 34 | func initOutPutLogger() { 35 | outputlogpath := filepath.Join(conf.ClientConf.BoardInfo.BoardPath, "output.log") 36 | hook := lumberjack.Logger{ 37 | Filename: outputlogpath, 38 | MaxSize: 10, 39 | MaxAge: 360, 40 | MaxBackups: 0, 41 | LocalTime: true, 42 | Compress: true, 43 | } 44 | encoderConfig := zapcore.EncoderConfig{ 45 | MessageKey: "msg", 46 | TimeKey: "time", 47 | //CallerKey: "file", 48 | LineEnding: zapcore.DefaultLineEnding, 49 | EncodeLevel: zapcore.LowercaseLevelEncoder, 50 | EncodeTime: formatEncodeTime, 51 | //EncodeCaller: zapcore.ShortCallerEncoder, 52 | } 53 | atomicLevel := zap.NewAtomicLevel() 54 | atomicLevel.SetLevel(zap.InfoLevel) 55 | var writes = []zapcore.WriteSyncer{zapcore.AddSync(&hook)} 56 | core := zapcore.NewCore( 57 | zapcore.NewJSONEncoder(encoderConfig), 58 | zapcore.NewMultiWriteSyncer(writes...), 59 | atomicLevel, 60 | ) 61 | caller := zap.AddCaller() 62 | development := zap.Development() 63 | OutPutLogger = zap.New(core, caller, development) 64 | } 65 | 66 | func formatEncodeTime(t time.Time, enc zapcore.PrimitiveArrayEncoder) { 67 | enc.AppendString(fmt.Sprintf("%d-%02d-%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())) 68 | } 69 | -------------------------------------------------------------------------------- /internal/rpc/client.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | type ID uint32 10 | 11 | type call struct { 12 | id ID 13 | ch chan<- RespMsg 14 | } 15 | 16 | type Client struct { 17 | conn *ClientConn 18 | 19 | sync.Mutex 20 | pending map[ID]call 21 | id ID 22 | closeOnce sync.Once 23 | closeCh <-chan struct{} 24 | } 25 | 26 | func newClient(codec *websocketCodec) *Client { 27 | ch := make(chan struct{}) 28 | c := &ClientConn{ 29 | codec: codec, 30 | closeCh: ch, 31 | } 32 | client := &Client{ 33 | closeCh: ch, 34 | conn: c, 35 | pending: make(map[ID]call), 36 | } 37 | client.receive() 38 | client.dispatch() 39 | return client 40 | } 41 | 42 | func (c *Client) dispatch() { 43 | go func() { 44 | for { 45 | select { 46 | case <-c.closeCh: 47 | c.Close() 48 | return 49 | } 50 | } 51 | }() 52 | } 53 | 54 | func (c *Client) receive() { 55 | go c.conn.readLoop(func(msg RespMsg) { 56 | c.Lock() 57 | id := ID(msg.Id) 58 | ca, exist := c.pending[id] 59 | if exist { 60 | delete(c.pending, id) 61 | } 62 | c.Unlock() 63 | 64 | if exist { 65 | ca.ch <- msg 66 | } 67 | }) 68 | } 69 | 70 | func (c *Client) nextId() ID { 71 | n := atomic.AddUint32((*uint32)(&c.id), 1) 72 | return ID(n) 73 | } 74 | 75 | func (c *Client) Call(ctx context.Context, msg *ReqMsg) (*RespMsg, error) { 76 | ch := make(chan RespMsg) 77 | ca := call{ 78 | id: c.nextId(), 79 | ch: ch, 80 | } 81 | msg.Id = uint64(ca.id) 82 | 83 | c.Lock() 84 | c.pending[ca.id] = ca 85 | c.Unlock() 86 | 87 | err := c.conn.codec.WriteMsg(ctx, msg) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | select { 93 | case resp := <-ch: 94 | return &resp, nil 95 | case <-ctx.Done(): 96 | c.Lock() 97 | delete(c.pending, ca.id) 98 | c.Unlock() 99 | return nil, ctx.Err() 100 | } 101 | } 102 | 103 | func (c *Client) Close() { 104 | c.conn.codec.close() 105 | c.closeOnce.Do(func() { 106 | c.Lock() 107 | defer c.Unlock() 108 | for id, ca := range c.pending { 109 | close(ca.ch) 110 | delete(c.pending, id) 111 | } 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /internal/rpc/client_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/golang/protobuf/proto" 7 | "net/http/httptest" 8 | "strings" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | type testService struct{} 14 | 15 | func (testService) HelloAction(body []byte) (proto.Message, error) { 16 | return &RespBody{Msg: "test hello"}, nil 17 | } 18 | 19 | func TestDialWebsocket(t *testing.T) { 20 | srv := NewServer() 21 | srv.Register("test", testService{}) 22 | s := httptest.NewServer(srv.WebsocketHandler([]string{"*"})) 23 | defer s.Close() 24 | defer srv.Close() 25 | 26 | wsURL := "ws:" + strings.TrimPrefix(s.URL, "http:") 27 | client, err := DialWebsocket(context.Background(), wsURL, "") 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | req := &ReqMsg{ 33 | Service: "test", 34 | Method: "hello", 35 | } 36 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 37 | resp, err := client.Call(ctx, req) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | cancel() 42 | fmt.Println(resp) 43 | } 44 | -------------------------------------------------------------------------------- /internal/rpc/codec.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/golang/protobuf/proto" 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | type protoCodec struct { 11 | conn *websocket.Conn 12 | closedCh chan struct{} //protocol连接关闭的检测管道 13 | } 14 | 15 | func (p *protoCodec) closed() <-chan struct{} { 16 | return p.closedCh 17 | } 18 | 19 | func (p *protoCodec) close() { 20 | select { 21 | case <-p.closedCh: 22 | default: 23 | close(p.closedCh) 24 | p.conn.Close() 25 | } 26 | } 27 | 28 | func (p *protoCodec) read(v proto.Message) error { 29 | _, r, err := p.conn.NextReader() 30 | if err != nil { 31 | return err 32 | } 33 | 34 | bs, err := io.ReadAll(r) 35 | if err != nil { 36 | return err 37 | } 38 | err = proto.Unmarshal(bs, v) 39 | return err 40 | } 41 | 42 | func (p *protoCodec) write(v proto.Message) error { 43 | w, err := p.conn.NextWriter(websocket.BinaryMessage) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | bs, _ := proto.Marshal(v) 49 | _, err = w.Write(bs) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | err = w.Close() 55 | return err 56 | } 57 | 58 | func (p *protoCodec) getConn() *websocket.Conn { 59 | return p.conn 60 | } 61 | 62 | func responseMessage(err error) *RespMsg { 63 | msg := &RespMsg{} 64 | ec, ok := err.(Error) 65 | errMsg := &RespBody{ 66 | Code: defaultErrorCode, 67 | Msg: err.Error(), 68 | } 69 | if ok { 70 | errMsg.Code = ec.ErrorCode() 71 | } 72 | msg.Body, _ = proto.Marshal(errMsg) 73 | return msg 74 | } 75 | -------------------------------------------------------------------------------- /internal/rpc/conn.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "github.com/ethereum/go-ethereum/log" 6 | 7 | "github.com/golang/protobuf/proto" 8 | ) 9 | 10 | type SrvConn struct { 11 | srv *Server 12 | codec *websocketCodec 13 | } 14 | 15 | func (c *SrvConn) readLoop() { 16 | defer c.codec.close() 17 | for { 18 | msg := ReqMsg{} 19 | err := c.codec.read(&msg) 20 | if _, ok := err.(*proto.ParseError); ok { 21 | c.codec.WriteMsg(context.Background(), responseMessage(&parseError{err.Error()})) 22 | continue 23 | } 24 | 25 | if err != nil { 26 | c.codec.Close() 27 | break 28 | } 29 | 30 | c.srv.handle(&msg, c) 31 | } 32 | } 33 | 34 | type ClientConn struct { 35 | codec *websocketCodec 36 | closeCh chan<- struct{} 37 | } 38 | 39 | func (c *ClientConn) readLoop(recv func(msg RespMsg)) { 40 | for { 41 | msg := RespMsg{} 42 | err := c.codec.read(&msg) 43 | if _, ok := err.(*proto.ParseError); ok { 44 | continue 45 | } 46 | 47 | if err != nil { 48 | log.Debug("client RPC connection read error", err) 49 | c.closeCh <- struct{}{} 50 | break 51 | } 52 | 53 | recv(msg) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/rpc/errors.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import "fmt" 4 | 5 | // Error wraps RPC errors, which contain an error code in addition to the message. 6 | type Error interface { 7 | Error() string // returns the message 8 | ErrorCode() int32 // returns the code 9 | } 10 | 11 | const ( 12 | defaultErrorCode = -10 - iota 13 | ParseErrorCode 14 | MethodNotFoundErrorCode 15 | ) 16 | 17 | type parseError struct{ message string } 18 | 19 | func (e *parseError) ErrorCode() int32 { return ParseErrorCode } 20 | 21 | func (e *parseError) Error() string { return e.message } 22 | 23 | type methodNotFoundError struct{ method string } 24 | 25 | func (e *methodNotFoundError) ErrorCode() int { return MethodNotFoundErrorCode } 26 | 27 | func (e *methodNotFoundError) Error() string { 28 | return fmt.Sprintf("the method %s does not exist/is not available", e.method) 29 | } 30 | -------------------------------------------------------------------------------- /internal/rpc/handler.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/golang/protobuf/proto" 7 | ) 8 | 9 | type handleWrapper func(id uint64, body []byte) *RespMsg 10 | type Handler func(body []byte) (proto.Message, error) 11 | 12 | func (s *Server) handle(msg *ReqMsg, c *SrvConn) { 13 | s.processMsg(func(ctx context.Context) { 14 | answer := s.callMethod(msg) 15 | c.codec.WriteMsg(ctx, answer) 16 | }) 17 | } 18 | 19 | func (s *Server) processMsg(fn func(ctx context.Context)) { 20 | s.handleWg.Add(1) 21 | go func() { 22 | ctx, cancel := context.WithCancel(context.Background()) 23 | defer s.handleWg.Done() 24 | defer cancel() 25 | fn(ctx) 26 | }() 27 | } 28 | 29 | func (s *Server) callMethod(msg *ReqMsg) *RespMsg { 30 | handler := s.router.lookup(msg.Service, msg.Method) 31 | if handler == nil { 32 | err := &methodNotFoundError{msg.Service + "." + msg.Method} 33 | answer := responseMessage(err) 34 | answer.Id = msg.Id 35 | return answer 36 | } 37 | answer := handler(msg.Id, msg.Body) 38 | return answer 39 | } 40 | -------------------------------------------------------------------------------- /internal/rpc/msg.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.19.4 5 | // source: msg.proto 6 | 7 | package rpc 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type ReqMsg struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` 29 | Id uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` 30 | Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"` 31 | Service string `protobuf:"bytes,4,opt,name=service,proto3" json:"service,omitempty"` 32 | Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"` 33 | } 34 | 35 | func (x *ReqMsg) Reset() { 36 | *x = ReqMsg{} 37 | if protoimpl.UnsafeEnabled { 38 | mi := &file_msg_proto_msgTypes[0] 39 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 40 | ms.StoreMessageInfo(mi) 41 | } 42 | } 43 | 44 | func (x *ReqMsg) String() string { 45 | return protoimpl.X.MessageStringOf(x) 46 | } 47 | 48 | func (*ReqMsg) ProtoMessage() {} 49 | 50 | func (x *ReqMsg) ProtoReflect() protoreflect.Message { 51 | mi := &file_msg_proto_msgTypes[0] 52 | if protoimpl.UnsafeEnabled && x != nil { 53 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 54 | if ms.LoadMessageInfo() == nil { 55 | ms.StoreMessageInfo(mi) 56 | } 57 | return ms 58 | } 59 | return mi.MessageOf(x) 60 | } 61 | 62 | // Deprecated: Use ReqMsg.ProtoReflect.Descriptor instead. 63 | func (*ReqMsg) Descriptor() ([]byte, []int) { 64 | return file_msg_proto_rawDescGZIP(), []int{0} 65 | } 66 | 67 | func (x *ReqMsg) GetVersion() int32 { 68 | if x != nil { 69 | return x.Version 70 | } 71 | return 0 72 | } 73 | 74 | func (x *ReqMsg) GetId() uint64 { 75 | if x != nil { 76 | return x.Id 77 | } 78 | return 0 79 | } 80 | 81 | func (x *ReqMsg) GetMethod() string { 82 | if x != nil { 83 | return x.Method 84 | } 85 | return "" 86 | } 87 | 88 | func (x *ReqMsg) GetService() string { 89 | if x != nil { 90 | return x.Service 91 | } 92 | return "" 93 | } 94 | 95 | func (x *ReqMsg) GetBody() []byte { 96 | if x != nil { 97 | return x.Body 98 | } 99 | return nil 100 | } 101 | 102 | type RespMsg struct { 103 | state protoimpl.MessageState 104 | sizeCache protoimpl.SizeCache 105 | unknownFields protoimpl.UnknownFields 106 | 107 | Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 108 | Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` 109 | } 110 | 111 | func (x *RespMsg) Reset() { 112 | *x = RespMsg{} 113 | if protoimpl.UnsafeEnabled { 114 | mi := &file_msg_proto_msgTypes[1] 115 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 116 | ms.StoreMessageInfo(mi) 117 | } 118 | } 119 | 120 | func (x *RespMsg) String() string { 121 | return protoimpl.X.MessageStringOf(x) 122 | } 123 | 124 | func (*RespMsg) ProtoMessage() {} 125 | 126 | func (x *RespMsg) ProtoReflect() protoreflect.Message { 127 | mi := &file_msg_proto_msgTypes[1] 128 | if protoimpl.UnsafeEnabled && x != nil { 129 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 130 | if ms.LoadMessageInfo() == nil { 131 | ms.StoreMessageInfo(mi) 132 | } 133 | return ms 134 | } 135 | return mi.MessageOf(x) 136 | } 137 | 138 | // Deprecated: Use RespMsg.ProtoReflect.Descriptor instead. 139 | func (*RespMsg) Descriptor() ([]byte, []int) { 140 | return file_msg_proto_rawDescGZIP(), []int{1} 141 | } 142 | 143 | func (x *RespMsg) GetId() uint64 { 144 | if x != nil { 145 | return x.Id 146 | } 147 | return 0 148 | } 149 | 150 | func (x *RespMsg) GetBody() []byte { 151 | if x != nil { 152 | return x.Body 153 | } 154 | return nil 155 | } 156 | 157 | type RespBody struct { 158 | state protoimpl.MessageState 159 | sizeCache protoimpl.SizeCache 160 | unknownFields protoimpl.UnknownFields 161 | 162 | Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 163 | Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` 164 | Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` 165 | } 166 | 167 | func (x *RespBody) Reset() { 168 | *x = RespBody{} 169 | if protoimpl.UnsafeEnabled { 170 | mi := &file_msg_proto_msgTypes[2] 171 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 172 | ms.StoreMessageInfo(mi) 173 | } 174 | } 175 | 176 | func (x *RespBody) String() string { 177 | return protoimpl.X.MessageStringOf(x) 178 | } 179 | 180 | func (*RespBody) ProtoMessage() {} 181 | 182 | func (x *RespBody) ProtoReflect() protoreflect.Message { 183 | mi := &file_msg_proto_msgTypes[2] 184 | if protoimpl.UnsafeEnabled && x != nil { 185 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 186 | if ms.LoadMessageInfo() == nil { 187 | ms.StoreMessageInfo(mi) 188 | } 189 | return ms 190 | } 191 | return mi.MessageOf(x) 192 | } 193 | 194 | // Deprecated: Use RespBody.ProtoReflect.Descriptor instead. 195 | func (*RespBody) Descriptor() ([]byte, []int) { 196 | return file_msg_proto_rawDescGZIP(), []int{2} 197 | } 198 | 199 | func (x *RespBody) GetCode() int32 { 200 | if x != nil { 201 | return x.Code 202 | } 203 | return 0 204 | } 205 | 206 | func (x *RespBody) GetMsg() string { 207 | if x != nil { 208 | return x.Msg 209 | } 210 | return "" 211 | } 212 | 213 | func (x *RespBody) GetData() []byte { 214 | if x != nil { 215 | return x.Data 216 | } 217 | return nil 218 | } 219 | 220 | var File_msg_proto protoreflect.FileDescriptor 221 | 222 | var file_msg_proto_rawDesc = []byte{ 223 | 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x72, 0x70, 0x63, 224 | 0x22, 0x78, 0x0a, 0x06, 0x52, 0x65, 0x71, 0x4d, 0x73, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 225 | 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 226 | 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 227 | 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 228 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x18, 0x0a, 0x07, 229 | 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 230 | 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x05, 231 | 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x2d, 0x0a, 0x07, 0x52, 0x65, 232 | 0x73, 0x70, 0x4d, 0x73, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 233 | 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 234 | 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x44, 0x0a, 0x08, 0x52, 0x65, 0x73, 235 | 0x70, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 236 | 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 237 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x64, 238 | 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 239 | 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x3b, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 240 | 0x33, 241 | } 242 | 243 | var ( 244 | file_msg_proto_rawDescOnce sync.Once 245 | file_msg_proto_rawDescData = file_msg_proto_rawDesc 246 | ) 247 | 248 | func file_msg_proto_rawDescGZIP() []byte { 249 | file_msg_proto_rawDescOnce.Do(func() { 250 | file_msg_proto_rawDescData = protoimpl.X.CompressGZIP(file_msg_proto_rawDescData) 251 | }) 252 | return file_msg_proto_rawDescData 253 | } 254 | 255 | var file_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 256 | var file_msg_proto_goTypes = []interface{}{ 257 | (*ReqMsg)(nil), // 0: rpc.ReqMsg 258 | (*RespMsg)(nil), // 1: rpc.RespMsg 259 | (*RespBody)(nil), // 2: rpc.RespBody 260 | } 261 | var file_msg_proto_depIdxs = []int32{ 262 | 0, // [0:0] is the sub-list for method output_type 263 | 0, // [0:0] is the sub-list for method input_type 264 | 0, // [0:0] is the sub-list for extension type_name 265 | 0, // [0:0] is the sub-list for extension extendee 266 | 0, // [0:0] is the sub-list for field type_name 267 | } 268 | 269 | func init() { file_msg_proto_init() } 270 | func file_msg_proto_init() { 271 | if File_msg_proto != nil { 272 | return 273 | } 274 | if !protoimpl.UnsafeEnabled { 275 | file_msg_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 276 | switch v := v.(*ReqMsg); i { 277 | case 0: 278 | return &v.state 279 | case 1: 280 | return &v.sizeCache 281 | case 2: 282 | return &v.unknownFields 283 | default: 284 | return nil 285 | } 286 | } 287 | file_msg_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 288 | switch v := v.(*RespMsg); i { 289 | case 0: 290 | return &v.state 291 | case 1: 292 | return &v.sizeCache 293 | case 2: 294 | return &v.unknownFields 295 | default: 296 | return nil 297 | } 298 | } 299 | file_msg_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 300 | switch v := v.(*RespBody); i { 301 | case 0: 302 | return &v.state 303 | case 1: 304 | return &v.sizeCache 305 | case 2: 306 | return &v.unknownFields 307 | default: 308 | return nil 309 | } 310 | } 311 | } 312 | type x struct{} 313 | out := protoimpl.TypeBuilder{ 314 | File: protoimpl.DescBuilder{ 315 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 316 | RawDescriptor: file_msg_proto_rawDesc, 317 | NumEnums: 0, 318 | NumMessages: 3, 319 | NumExtensions: 0, 320 | NumServices: 0, 321 | }, 322 | GoTypes: file_msg_proto_goTypes, 323 | DependencyIndexes: file_msg_proto_depIdxs, 324 | MessageInfos: file_msg_proto_msgTypes, 325 | }.Build() 326 | File_msg_proto = out.File 327 | file_msg_proto_rawDesc = nil 328 | file_msg_proto_goTypes = nil 329 | file_msg_proto_depIdxs = nil 330 | } 331 | -------------------------------------------------------------------------------- /internal/rpc/msg.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package rpc; 4 | 5 | option go_package = "./;rpc"; 6 | 7 | message ReqMsg { 8 | int32 version = 1; 9 | uint64 id = 2; 10 | string method = 3; 11 | string service = 4; 12 | bytes body = 5; 13 | } 14 | 15 | message RespMsg { 16 | uint64 id = 1; 17 | bytes body = 2; 18 | } 19 | 20 | message RespBody { 21 | int32 code = 1; 22 | string msg = 2; 23 | bytes data = 3; 24 | } -------------------------------------------------------------------------------- /internal/rpc/server.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "sync" 5 | 6 | mapset "github.com/deckarep/golang-set" 7 | ) 8 | 9 | type Server struct { 10 | running int32 11 | conns mapset.Set 12 | 13 | handleWg sync.WaitGroup 14 | router *serviceRouter 15 | } 16 | 17 | func NewServer() *Server { 18 | s := &Server{ 19 | conns: mapset.NewSet(), 20 | router: newServiceRouter(), 21 | } 22 | return s 23 | } 24 | 25 | func (s *Server) serve(codec *websocketCodec) { 26 | c := &SrvConn{ 27 | srv: s, 28 | codec: codec, 29 | } 30 | // Add the conn to the set so it can be closed by Stop. 31 | s.conns.Add(c) 32 | defer s.conns.Remove(c) 33 | 34 | go c.readLoop() 35 | } 36 | 37 | func (s *Server) Register(name string, service interface{}) error { 38 | return s.router.registerName(name, service) 39 | } 40 | 41 | func (s *Server) Close() { 42 | s.conns.Each(func(c interface{}) bool { 43 | c.(websocketCodec).close() 44 | return true 45 | }) 46 | s.handleWg.Wait() 47 | } 48 | -------------------------------------------------------------------------------- /internal/rpc/service.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "sync" 8 | "unicode" 9 | 10 | "github.com/golang/protobuf/proto" 11 | ) 12 | 13 | const methodSuffix = "Action" 14 | 15 | type serviceRouter struct { 16 | mu sync.Mutex 17 | services map[string]service 18 | } 19 | 20 | func newServiceRouter() *serviceRouter { 21 | return &serviceRouter{ 22 | services: make(map[string]service), 23 | } 24 | } 25 | 26 | // service represents a registered object. 27 | type service struct { 28 | name string 29 | handlers map[string]handleWrapper 30 | } 31 | 32 | func (r *serviceRouter) registerName(name string, svc interface{}) error { 33 | recvVal := reflect.ValueOf(svc) 34 | if name == "" { 35 | return fmt.Errorf("no service name for type %s", recvVal.Type().String()) 36 | } 37 | handlers := suitableHandlers(recvVal) 38 | if len(handlers) == 0 { 39 | return fmt.Errorf("service %T doesn't have any suitable public method", recvVal) 40 | } 41 | 42 | r.mu.Lock() 43 | defer r.mu.Unlock() 44 | if r.services == nil { 45 | r.services = make(map[string]service) 46 | } 47 | 48 | s, ok := r.services[name] 49 | if !ok { 50 | s = service{ 51 | name: name, 52 | handlers: make(map[string]handleWrapper), 53 | } 54 | r.services[name] = s 55 | } 56 | for name, h := range handlers { 57 | s.handlers[name] = h 58 | } 59 | return nil 60 | } 61 | 62 | func (r *serviceRouter) lookup(srvName, method string) handleWrapper { 63 | r.mu.Lock() 64 | defer r.mu.Unlock() 65 | return r.services[srvName].handlers[method] 66 | } 67 | 68 | func suitableHandlers(receiver reflect.Value) map[string]handleWrapper { 69 | typ := receiver.Type() 70 | handlers := make(map[string]handleWrapper) 71 | for m := 0; m < typ.NumMethod(); m++ { 72 | method := typ.Method(m) 73 | if method.PkgPath != "" { 74 | continue // private method 75 | } 76 | if strings.HasSuffix(method.Name, methodSuffix) { 77 | fn := method.Func 78 | name := formatName(method.Name) 79 | handlers[name] = func(id uint64, body []byte) *RespMsg { 80 | results := fn.Call([]reflect.Value{receiver, reflect.ValueOf(body)}) 81 | var ( 82 | rel proto.Message 83 | err error 84 | ) 85 | if !results[0].IsNil() { 86 | rel = results[0].Interface().(proto.Message) 87 | } 88 | 89 | if !results[1].IsNil() { 90 | err = results[1].Interface().(error) 91 | resp := responseMessage(err) 92 | resp.Id = uint64(id) 93 | return resp 94 | } 95 | 96 | resp := &RespMsg{ 97 | Id: uint64(id), 98 | } 99 | bs, _ := proto.Marshal(rel) 100 | resp.Body = bs 101 | return resp 102 | } 103 | } 104 | } 105 | 106 | return handlers 107 | } 108 | 109 | func formatName(name string) string { 110 | name = strings.TrimSuffix(name, methodSuffix) 111 | ret := []rune(name) 112 | if len(ret) > 0 { 113 | ret[0] = unicode.ToLower(ret[0]) 114 | } 115 | return string(ret) 116 | } 117 | -------------------------------------------------------------------------------- /internal/rpc/websocket.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | "github.com/ethereum/go-ethereum/log" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "strings" 12 | "sync" 13 | "time" 14 | 15 | mapset "github.com/deckarep/golang-set" 16 | "github.com/golang/protobuf/proto" 17 | "github.com/gorilla/websocket" 18 | ) 19 | 20 | const ( 21 | wsBuffRead = 1024 22 | wsBuffWrite = 1024 23 | wsMsgSizeLimit = 8 * 1024 * 1024 24 | 25 | wsHeartbeatInterval = 120 * time.Second 26 | wsWriteTimeout = 10 * time.Second 27 | wsReadTimeout = 10 * time.Second 28 | ) 29 | 30 | var wsBufferPool = new(sync.Pool) 31 | 32 | func (s *Server) WebsocketHandler(allowedOrigins []string) http.Handler { 33 | var upgrader = websocket.Upgrader{ 34 | ReadBufferSize: wsBuffRead, 35 | WriteBufferSize: wsBuffWrite, 36 | WriteBufferPool: wsBufferPool, 37 | CheckOrigin: wsHandshakeValidator(allowedOrigins), 38 | } 39 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 | conn, err := upgrader.Upgrade(w, r, nil) 41 | if err != nil { 42 | log.Debug("WebSocket upgrade failed", "err", err) 43 | return 44 | } 45 | codec := newWebsocketCodec(conn, r.Host, r.Header) 46 | s.serve(codec) 47 | }) 48 | } 49 | 50 | func wsHandshakeValidator(allowedOrigins []string) func(*http.Request) bool { 51 | origins := mapset.NewSet() 52 | allowAllOrigins := false 53 | 54 | for _, origin := range allowedOrigins { 55 | if origin == "*" { 56 | allowAllOrigins = true 57 | } 58 | if origin != "" { 59 | origins.Add(origin) 60 | } 61 | } 62 | // allow localhost if no allowedOrigins are specified. 63 | if len(origins.ToSlice()) == 0 { 64 | origins.Add("http://localhost") 65 | if hostname, err := os.Hostname(); err == nil { 66 | origins.Add("http://" + hostname) 67 | } 68 | } 69 | log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v", origins.ToSlice())) 70 | 71 | f := func(req *http.Request) bool { 72 | // Skip origin verification if no Origin header is present. The origin check 73 | // is supposed to protect against browser based attacks. Browsers always set 74 | // Origin. Non-browser software can put anything in origin and checking it doesn't 75 | // provide additional security. 76 | if _, ok := req.Header["Origin"]; !ok { 77 | return true 78 | } 79 | // Verify origin against allow list. 80 | origin := strings.ToLower(req.Header.Get("Origin")) 81 | if allowAllOrigins || originIsAllowed(origins, origin) { 82 | return true 83 | } 84 | log.Warn("Rejected WebSocket connection", "origin", origin) 85 | return false 86 | } 87 | 88 | return f 89 | } 90 | 91 | func originIsAllowed(allowedOrigins mapset.Set, browserOrigin string) bool { 92 | it := allowedOrigins.Iterator() 93 | for origin := range it.C { 94 | if ruleAllowsOrigin(origin.(string), browserOrigin) { 95 | return true 96 | } 97 | } 98 | return false 99 | } 100 | 101 | func ruleAllowsOrigin(allowedOrigin string, browserOrigin string) bool { 102 | var ( 103 | allowedScheme, allowedHostname, allowedPort string 104 | browserScheme, browserHostname, browserPort string 105 | err error 106 | ) 107 | allowedScheme, allowedHostname, allowedPort, err = parseOriginURL(allowedOrigin) 108 | if err != nil { 109 | log.Warn("Error parsing allowed origin specification", "spec", allowedOrigin, "error", err) 110 | return false 111 | } 112 | browserScheme, browserHostname, browserPort, err = parseOriginURL(browserOrigin) 113 | if err != nil { 114 | log.Warn("Error parsing browser 'Origin' field", "Origin", browserOrigin, "error", err) 115 | return false 116 | } 117 | if allowedScheme != "" && allowedScheme != browserScheme { 118 | return false 119 | } 120 | if allowedHostname != "" && allowedHostname != browserHostname { 121 | return false 122 | } 123 | if allowedPort != "" && allowedPort != browserPort { 124 | return false 125 | } 126 | return true 127 | } 128 | 129 | func parseOriginURL(origin string) (string, string, string, error) { 130 | parsedURL, err := url.Parse(strings.ToLower(origin)) 131 | if err != nil { 132 | return "", "", "", err 133 | } 134 | var scheme, hostname, port string 135 | if strings.Contains(origin, "://") { 136 | scheme = parsedURL.Scheme 137 | hostname = parsedURL.Hostname() 138 | port = parsedURL.Port() 139 | } else { 140 | scheme = "" 141 | hostname = parsedURL.Scheme 142 | port = parsedURL.Opaque 143 | if hostname == "" { 144 | hostname = origin 145 | } 146 | } 147 | return scheme, hostname, port, nil 148 | } 149 | 150 | func DialWebsocket(ctx context.Context, endpoint, origin string) (*Client, error) { 151 | dialer := websocket.Dialer{ 152 | ReadBufferSize: wsBuffRead, 153 | WriteBufferSize: wsBuffWrite, 154 | WriteBufferPool: wsBufferPool, 155 | } 156 | return DialWebsocketWithDialer(ctx, endpoint, origin, dialer) 157 | } 158 | 159 | func wsClientHeaders(endpoint, origin string) (string, http.Header, error) { 160 | endpointURL, err := url.Parse(endpoint) 161 | if err != nil { 162 | return endpoint, nil, err 163 | } 164 | header := make(http.Header) 165 | if origin != "" { 166 | header.Add("origin", origin) 167 | } 168 | if endpointURL.User != nil { 169 | b64auth := base64.StdEncoding.EncodeToString([]byte(endpointURL.User.String())) 170 | header.Add("authorization", "Basic "+b64auth) 171 | endpointURL.User = nil 172 | } 173 | return endpointURL.String(), header, nil 174 | } 175 | 176 | func DialWebsocketWithDialer(ctx context.Context, endpoint, origin string, dialer websocket.Dialer) (*Client, error) { 177 | endpoint, header, err := wsClientHeaders(endpoint, origin) 178 | if err != nil { 179 | return nil, err 180 | } 181 | conn, _, err := dialer.DialContext(ctx, endpoint, header) 182 | if err != nil { 183 | return nil, err 184 | } 185 | codec := newWebsocketCodec(conn, endpoint, header) 186 | return newClient(codec), nil 187 | } 188 | 189 | type websocketCodec struct { 190 | *protoCodec 191 | 192 | mu sync.Mutex 193 | wg sync.WaitGroup 194 | pingReset chan struct{} 195 | } 196 | 197 | func newWebsocketCodec(conn *websocket.Conn, host string, req http.Header) *websocketCodec { 198 | conn.SetReadLimit(wsMsgSizeLimit) 199 | conn.SetPongHandler(func(appData string) error { 200 | conn.SetReadDeadline(time.Time{}) 201 | return nil 202 | }) 203 | wc := &websocketCodec{ 204 | protoCodec: &protoCodec{ 205 | conn: conn, 206 | closedCh: make(chan struct{}), 207 | }, 208 | pingReset: make(chan struct{}, 1), 209 | } 210 | 211 | // Start pinger. 212 | wc.wg.Add(1) 213 | go wc.pingLoop() 214 | return wc 215 | } 216 | 217 | func (w *websocketCodec) Close() { 218 | w.close() 219 | w.wg.Wait() 220 | } 221 | 222 | func (w *websocketCodec) WriteMsg(ctx context.Context, v proto.Message) error { 223 | w.mu.Lock() 224 | defer w.mu.Unlock() 225 | 226 | deadline, ok := ctx.Deadline() 227 | if !ok { 228 | deadline = time.Now().Add(wsWriteTimeout) 229 | } 230 | w.getConn().SetWriteDeadline(deadline) 231 | err := w.write(v) 232 | if err == nil { 233 | // Notify pingLoop to delay the next idle ping. 234 | select { 235 | case w.pingReset <- struct{}{}: 236 | default: 237 | } 238 | } 239 | return err 240 | } 241 | 242 | func (w *websocketCodec) pingLoop() { 243 | var timer = time.NewTimer(wsHeartbeatInterval) 244 | defer w.wg.Done() 245 | defer timer.Stop() 246 | 247 | for { 248 | select { 249 | case <-w.closed(): 250 | return 251 | case <-w.pingReset: 252 | if !timer.Stop() { 253 | <-timer.C 254 | } 255 | timer.Reset(wsHeartbeatInterval) 256 | case <-timer.C: 257 | w.mu.Lock() 258 | w.conn.SetWriteDeadline(time.Now().Add(wsWriteTimeout)) 259 | w.conn.WriteMessage(websocket.PingMessage, nil) 260 | w.conn.SetReadDeadline(time.Now().Add(wsReadTimeout)) 261 | w.mu.Unlock() 262 | timer.Reset(wsHeartbeatInterval) 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /module/file.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.19.4 5 | // source: file.proto 6 | 7 | package module 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type FileUploadInfo struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | FileId string `protobuf:"bytes,1,opt,name=file_id,json=fileId,proto3" json:"file_id,omitempty"` 29 | FileHash string `protobuf:"bytes,2,opt,name=file_hash,json=fileHash,proto3" json:"file_hash,omitempty"` 30 | Backups string `protobuf:"bytes,3,opt,name=backups,proto3" json:"backups,omitempty"` 31 | BlockTotal int32 `protobuf:"varint,4,opt,name=block_total,json=blockTotal,proto3" json:"block_total,omitempty"` //block_total 32 | BlockSize int32 `protobuf:"varint,5,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"` 33 | BlockIndex int32 `protobuf:"varint,6,opt,name=block_index,json=blockIndex,proto3" json:"block_index,omitempty"` //block_index 34 | Data []byte `protobuf:"bytes,7,opt,name=data,proto3" json:"data,omitempty"` 35 | } 36 | 37 | func (x *FileUploadInfo) Reset() { 38 | *x = FileUploadInfo{} 39 | if protoimpl.UnsafeEnabled { 40 | mi := &file_file_proto_msgTypes[0] 41 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 42 | ms.StoreMessageInfo(mi) 43 | } 44 | } 45 | 46 | func (x *FileUploadInfo) String() string { 47 | return protoimpl.X.MessageStringOf(x) 48 | } 49 | 50 | func (*FileUploadInfo) ProtoMessage() {} 51 | 52 | func (x *FileUploadInfo) ProtoReflect() protoreflect.Message { 53 | mi := &file_file_proto_msgTypes[0] 54 | if protoimpl.UnsafeEnabled && x != nil { 55 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 56 | if ms.LoadMessageInfo() == nil { 57 | ms.StoreMessageInfo(mi) 58 | } 59 | return ms 60 | } 61 | return mi.MessageOf(x) 62 | } 63 | 64 | // Deprecated: Use FileUploadInfo.ProtoReflect.Descriptor instead. 65 | func (*FileUploadInfo) Descriptor() ([]byte, []int) { 66 | return file_file_proto_rawDescGZIP(), []int{0} 67 | } 68 | 69 | func (x *FileUploadInfo) GetFileId() string { 70 | if x != nil { 71 | return x.FileId 72 | } 73 | return "" 74 | } 75 | 76 | func (x *FileUploadInfo) GetFileHash() string { 77 | if x != nil { 78 | return x.FileHash 79 | } 80 | return "" 81 | } 82 | 83 | func (x *FileUploadInfo) GetBackups() string { 84 | if x != nil { 85 | return x.Backups 86 | } 87 | return "" 88 | } 89 | 90 | func (x *FileUploadInfo) GetBlockTotal() int32 { 91 | if x != nil { 92 | return x.BlockTotal 93 | } 94 | return 0 95 | } 96 | 97 | func (x *FileUploadInfo) GetBlockSize() int32 { 98 | if x != nil { 99 | return x.BlockSize 100 | } 101 | return 0 102 | } 103 | 104 | func (x *FileUploadInfo) GetBlockIndex() int32 { 105 | if x != nil { 106 | return x.BlockIndex 107 | } 108 | return 0 109 | } 110 | 111 | func (x *FileUploadInfo) GetData() []byte { 112 | if x != nil { 113 | return x.Data 114 | } 115 | return nil 116 | } 117 | 118 | type FileDownloadInfo struct { 119 | state protoimpl.MessageState 120 | sizeCache protoimpl.SizeCache 121 | unknownFields protoimpl.UnknownFields 122 | 123 | FileId string `protobuf:"bytes,1,opt,name=file_id,json=fileId,proto3" json:"file_id,omitempty"` 124 | BlockTotal int32 `protobuf:"varint,2,opt,name=block_total,json=blockTotal,proto3" json:"block_total,omitempty"` 125 | BlockSize int32 `protobuf:"varint,3,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"` 126 | BlockIndex int32 `protobuf:"varint,4,opt,name=block_index,json=blockIndex,proto3" json:"block_index,omitempty"` 127 | Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` 128 | } 129 | 130 | func (x *FileDownloadInfo) Reset() { 131 | *x = FileDownloadInfo{} 132 | if protoimpl.UnsafeEnabled { 133 | mi := &file_file_proto_msgTypes[1] 134 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 135 | ms.StoreMessageInfo(mi) 136 | } 137 | } 138 | 139 | func (x *FileDownloadInfo) String() string { 140 | return protoimpl.X.MessageStringOf(x) 141 | } 142 | 143 | func (*FileDownloadInfo) ProtoMessage() {} 144 | 145 | func (x *FileDownloadInfo) ProtoReflect() protoreflect.Message { 146 | mi := &file_file_proto_msgTypes[1] 147 | if protoimpl.UnsafeEnabled && x != nil { 148 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 149 | if ms.LoadMessageInfo() == nil { 150 | ms.StoreMessageInfo(mi) 151 | } 152 | return ms 153 | } 154 | return mi.MessageOf(x) 155 | } 156 | 157 | // Deprecated: Use FileDownloadInfo.ProtoReflect.Descriptor instead. 158 | func (*FileDownloadInfo) Descriptor() ([]byte, []int) { 159 | return file_file_proto_rawDescGZIP(), []int{1} 160 | } 161 | 162 | func (x *FileDownloadInfo) GetFileId() string { 163 | if x != nil { 164 | return x.FileId 165 | } 166 | return "" 167 | } 168 | 169 | func (x *FileDownloadInfo) GetBlockTotal() int32 { 170 | if x != nil { 171 | return x.BlockTotal 172 | } 173 | return 0 174 | } 175 | 176 | func (x *FileDownloadInfo) GetBlockSize() int32 { 177 | if x != nil { 178 | return x.BlockSize 179 | } 180 | return 0 181 | } 182 | 183 | func (x *FileDownloadInfo) GetBlockIndex() int32 { 184 | if x != nil { 185 | return x.BlockIndex 186 | } 187 | return 0 188 | } 189 | 190 | func (x *FileDownloadInfo) GetData() []byte { 191 | if x != nil { 192 | return x.Data 193 | } 194 | return nil 195 | } 196 | 197 | type FileDownloadReq struct { 198 | state protoimpl.MessageState 199 | sizeCache protoimpl.SizeCache 200 | unknownFields protoimpl.UnknownFields 201 | 202 | FileId string `protobuf:"bytes,1,opt,name=file_id,json=fileId,proto3" json:"file_id,omitempty"` 203 | WalletAddress string `protobuf:"bytes,2,opt,name=walletAddress,proto3" json:"walletAddress,omitempty"` 204 | BlockIndex int32 `protobuf:"varint,3,opt,name=block_index,json=blockIndex,proto3" json:"block_index,omitempty"` 205 | } 206 | 207 | func (x *FileDownloadReq) Reset() { 208 | *x = FileDownloadReq{} 209 | if protoimpl.UnsafeEnabled { 210 | mi := &file_file_proto_msgTypes[2] 211 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 212 | ms.StoreMessageInfo(mi) 213 | } 214 | } 215 | 216 | func (x *FileDownloadReq) String() string { 217 | return protoimpl.X.MessageStringOf(x) 218 | } 219 | 220 | func (*FileDownloadReq) ProtoMessage() {} 221 | 222 | func (x *FileDownloadReq) ProtoReflect() protoreflect.Message { 223 | mi := &file_file_proto_msgTypes[2] 224 | if protoimpl.UnsafeEnabled && x != nil { 225 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 226 | if ms.LoadMessageInfo() == nil { 227 | ms.StoreMessageInfo(mi) 228 | } 229 | return ms 230 | } 231 | return mi.MessageOf(x) 232 | } 233 | 234 | // Deprecated: Use FileDownloadReq.ProtoReflect.Descriptor instead. 235 | func (*FileDownloadReq) Descriptor() ([]byte, []int) { 236 | return file_file_proto_rawDescGZIP(), []int{2} 237 | } 238 | 239 | func (x *FileDownloadReq) GetFileId() string { 240 | if x != nil { 241 | return x.FileId 242 | } 243 | return "" 244 | } 245 | 246 | func (x *FileDownloadReq) GetWalletAddress() string { 247 | if x != nil { 248 | return x.WalletAddress 249 | } 250 | return "" 251 | } 252 | 253 | func (x *FileDownloadReq) GetBlockIndex() int32 { 254 | if x != nil { 255 | return x.BlockIndex 256 | } 257 | return 0 258 | } 259 | 260 | var File_file_proto protoreflect.FileDescriptor 261 | 262 | var file_file_proto_rawDesc = []byte{ 263 | 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x63, 0x6c, 264 | 0x69, 0x65, 0x6e, 0x74, 0x22, 0xd7, 0x01, 0x0a, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x75, 0x70, 265 | 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69, 0x6c, 266 | 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 267 | 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 268 | 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 269 | 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 270 | 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 271 | 0x63, 0x6b, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 272 | 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 273 | 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 274 | 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 275 | 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 276 | 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 277 | 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xa2, 278 | 0x01, 0x0a, 0x12, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 279 | 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x64, 280 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1f, 281 | 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 282 | 0x01, 0x28, 0x05, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 283 | 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 284 | 0x01, 0x28, 0x05, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 285 | 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 286 | 0x01, 0x28, 0x05, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 287 | 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 288 | 0x61, 0x74, 0x61, 0x22, 0x73, 0x0a, 0x11, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 289 | 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x72, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x69, 0x6c, 0x65, 290 | 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x65, 0x49, 291 | 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 292 | 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 293 | 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 294 | 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x62, 0x6c, 295 | 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x3b, 0x6d, 296 | 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 297 | } 298 | 299 | var ( 300 | file_file_proto_rawDescOnce sync.Once 301 | file_file_proto_rawDescData = file_file_proto_rawDesc 302 | ) 303 | 304 | func file_file_proto_rawDescGZIP() []byte { 305 | file_file_proto_rawDescOnce.Do(func() { 306 | file_file_proto_rawDescData = protoimpl.X.CompressGZIP(file_file_proto_rawDescData) 307 | }) 308 | return file_file_proto_rawDescData 309 | } 310 | 311 | var file_file_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 312 | var file_file_proto_goTypes = []interface{}{ 313 | (*FileUploadInfo)(nil), // 0: client.file_upload_info 314 | (*FileDownloadInfo)(nil), // 1: client.file_download_info 315 | (*FileDownloadReq)(nil), // 2: client.file_download_req 316 | } 317 | var file_file_proto_depIdxs = []int32{ 318 | 0, // [0:0] is the sub-list for method output_type 319 | 0, // [0:0] is the sub-list for method input_type 320 | 0, // [0:0] is the sub-list for extension type_name 321 | 0, // [0:0] is the sub-list for extension extendee 322 | 0, // [0:0] is the sub-list for field type_name 323 | } 324 | 325 | func init() { file_file_proto_init() } 326 | func file_file_proto_init() { 327 | if File_file_proto != nil { 328 | return 329 | } 330 | if !protoimpl.UnsafeEnabled { 331 | file_file_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 332 | switch v := v.(*FileUploadInfo); i { 333 | case 0: 334 | return &v.state 335 | case 1: 336 | return &v.sizeCache 337 | case 2: 338 | return &v.unknownFields 339 | default: 340 | return nil 341 | } 342 | } 343 | file_file_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 344 | switch v := v.(*FileDownloadInfo); i { 345 | case 0: 346 | return &v.state 347 | case 1: 348 | return &v.sizeCache 349 | case 2: 350 | return &v.unknownFields 351 | default: 352 | return nil 353 | } 354 | } 355 | file_file_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 356 | switch v := v.(*FileDownloadReq); i { 357 | case 0: 358 | return &v.state 359 | case 1: 360 | return &v.sizeCache 361 | case 2: 362 | return &v.unknownFields 363 | default: 364 | return nil 365 | } 366 | } 367 | } 368 | type x struct{} 369 | out := protoimpl.TypeBuilder{ 370 | File: protoimpl.DescBuilder{ 371 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 372 | RawDescriptor: file_file_proto_rawDesc, 373 | NumEnums: 0, 374 | NumMessages: 3, 375 | NumExtensions: 0, 376 | NumServices: 0, 377 | }, 378 | GoTypes: file_file_proto_goTypes, 379 | DependencyIndexes: file_file_proto_depIdxs, 380 | MessageInfos: file_file_proto_msgTypes, 381 | }.Build() 382 | File_file_proto = out.File 383 | file_file_proto_rawDesc = nil 384 | file_file_proto_goTypes = nil 385 | file_file_proto_depIdxs = nil 386 | } 387 | -------------------------------------------------------------------------------- /module/file.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | option go_package = "./;module"; 3 | package client; 4 | 5 | message file_upload_info { 6 | string file_id = 1; 7 | string file_hash = 2; 8 | string backups = 3; 9 | int32 block_total = 4;//block_total 10 | int32 block_size=5; 11 | int32 block_index=6;//block_index 12 | bytes data=7; 13 | } 14 | 15 | message file_download_info { 16 | string file_id=1; 17 | int32 block_total=2; 18 | int32 block_size=3; 19 | int32 block_index=4; 20 | bytes data=5; 21 | } 22 | 23 | message file_download_req{ 24 | string file_id=1; 25 | string walletAddress=2; 26 | int32 block_index=3; 27 | } -------------------------------------------------------------------------------- /module/services.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | const SchedulerPort = "8081" 4 | 5 | const CtlServiceName = "wservice" 6 | const ScheduleServiceName = "schedule" 7 | 8 | const UploadService = "writefile" 9 | const DownloadService = "readfile" 10 | 11 | type CtlService struct{} 12 | type ScheduleService struct{} 13 | -------------------------------------------------------------------------------- /test/file_decrypt_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestFileDecrypt(t *testing.T) { 10 | conf.ClientConf.PathInfo.InstallPath = "" 11 | decryptpath := "" 12 | err := client.FileDecrypt(decryptpath) 13 | if err != nil { 14 | t.Error(err) 15 | } else { 16 | t.Log("success") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/file_delete_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestFileDelete(t *testing.T) { 10 | //config file 11 | conf.ClientConf.ChainData.CessRpcAddr = "" 12 | conf.ClientConf.ChainData.IdAccountPhraseOrSeed = "" 13 | conf.ClientConf.BoardInfo.BoardPath = "" 14 | 15 | //param 16 | fileid := "" 17 | 18 | err := client.FileDelete(fileid) 19 | t.Fatal(err) 20 | } 21 | -------------------------------------------------------------------------------- /test/file_download_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestDownload(t *testing.T) { 10 | //config file 11 | conf.ClientConf.ChainData.CessRpcAddr = "" 12 | conf.ClientConf.BoardInfo.BoardPath = "" 13 | conf.ClientConf.PathInfo.InstallPath = "" 14 | conf.ClientConf.ChainData.WalletAddress = "" 15 | 16 | //param 17 | fileid := "" 18 | 19 | err := client.FileDownload(fileid) 20 | t.Fatal(err) 21 | } 22 | -------------------------------------------------------------------------------- /test/file_upload_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestFileUpload(t *testing.T) { 10 | //config file 11 | conf.ClientConf.ChainData.CessRpcAddr = "" 12 | conf.ClientConf.BoardInfo.BoardPath = "" 13 | conf.ClientConf.PathInfo.KeyPath = "" 14 | conf.ClientConf.ChainData.IdAccountPhraseOrSeed = "" 15 | conf.ClientConf.ChainData.WalletAddress = "" 16 | 17 | //param 18 | path := "" 19 | backups := "" 20 | PrivateKey := "" 21 | err := client.FileUpload(path, backups, PrivateKey) 22 | t.Fatal(err) 23 | } 24 | -------------------------------------------------------------------------------- /test/purchase_free_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestObtainFromFaucet(t *testing.T) { 10 | //config file 11 | conf.ClientConf.BoardInfo.BoardPath = "" 12 | conf.ClientConf.ChainData.FaucetAddress = "" 13 | 14 | //param 15 | pkg := "" 16 | err := client.ObtainFromFaucet(pkg) 17 | t.Fatal(err) 18 | } 19 | -------------------------------------------------------------------------------- /test/purchase_storage_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestExpansion(t *testing.T) { 10 | //config file 11 | conf.ClientConf.ChainData.CessRpcAddr = "" 12 | conf.ClientConf.ChainData.IdAccountPhraseOrSeed = "" 13 | conf.ClientConf.BoardInfo.BoardPath = "" 14 | 15 | //param 16 | quantity := 1 17 | duration := 1 18 | expected := 0 19 | err := client.Expansion(quantity, duration, expected) 20 | t.Fatal(err) 21 | } 22 | -------------------------------------------------------------------------------- /test/query_file_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestFindFile(t *testing.T) { 10 | //config file 11 | conf.ClientConf.ChainData.CessRpcAddr = "" 12 | conf.ClientConf.BoardInfo.BoardPath = "" 13 | conf.ClientConf.ChainData.AccountPublicKey = "" 14 | 15 | //param 16 | fileid := "" 17 | 18 | err := client.QueryFile(fileid) 19 | t.Fatal(err) 20 | } 21 | -------------------------------------------------------------------------------- /test/query_price_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestFindPrice(t *testing.T) { 10 | //config file 11 | conf.ClientConf.ChainData.CessRpcAddr = "" 12 | conf.ClientConf.BoardInfo.BoardPath = "" 13 | 14 | err := client.QueryPrice() 15 | t.Fatal(err) 16 | } 17 | -------------------------------------------------------------------------------- /test/query_space_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "cess-portal/client" 5 | "cess-portal/conf" 6 | "testing" 7 | ) 8 | 9 | func TestFindPurchasedSpace(t *testing.T) { 10 | //config file 11 | conf.ClientConf.ChainData.CessRpcAddr = "" 12 | conf.ClientConf.ChainData.AccountPublicKey = "" 13 | conf.ClientConf.BoardInfo.BoardPath = "" 14 | 15 | err := client.QueryPurchasedSpace() 16 | t.Fatal(err) 17 | } 18 | -------------------------------------------------------------------------------- /tools/aes.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | ) 8 | 9 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte { 10 | padding := blockSize - len(ciphertext)%blockSize 11 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 12 | return append(ciphertext, padtext...) 13 | } 14 | 15 | func PKCS5UnPadding(origData []byte) []byte { 16 | length := len(origData) 17 | unpadding := int(origData[length-1]) 18 | return origData[:(length - unpadding)] 19 | } 20 | 21 | func AesEncrypt(origData, key []byte) ([]byte, error) { 22 | block, err := aes.NewCipher(key) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | blockSize := block.BlockSize() 28 | origData = PKCS5Padding(origData, blockSize) 29 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) 30 | crypted := make([]byte, len(origData)) 31 | blockMode.CryptBlocks(crypted, origData) 32 | return crypted, nil 33 | } 34 | 35 | func AesDecrypt(crypted, key []byte) ([]byte, error) { 36 | block, err := aes.NewCipher(key) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | blockSize := block.BlockSize() 42 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) 43 | origData := make([]byte, len(crypted)) 44 | blockMode.CryptBlocks(origData, crypted) 45 | origData = PKCS5UnPadding(origData) 46 | return origData, nil 47 | } 48 | -------------------------------------------------------------------------------- /tools/tool.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "github.com/btcsuite/btcutil/base58" 11 | "github.com/bwmarrin/snowflake" 12 | "golang.org/x/crypto/blake2b" 13 | "io" 14 | "io/ioutil" 15 | "mime/multipart" 16 | "net/http" 17 | "os" 18 | ) 19 | 20 | var ( 21 | Black = string([]byte{27, 91, 57, 48, 109}) 22 | Red = string([]byte{27, 91, 57, 49, 109}) 23 | Green = string([]byte{27, 91, 57, 50, 109}) 24 | Yellow = string([]byte{27, 91, 57, 51, 109}) 25 | Blue = string([]byte{27, 91, 57, 52, 109}) 26 | Magenta = string([]byte{27, 91, 57, 53, 109}) 27 | Cyan = string([]byte{27, 91, 57, 54, 109}) 28 | White = string([]byte{27, 91, 57, 55, 59, 52, 48, 109}) 29 | Reset = string([]byte{27, 91, 48, 109}) 30 | DisableColor = false 31 | ) 32 | var ( 33 | SSPrefix = []byte{0x53, 0x53, 0x35, 0x38, 0x50, 0x52, 0x45} 34 | PolkadotPrefix = []byte{0x00} 35 | KsmPrefix = []byte{0x02} 36 | KatalPrefix = []byte{0x04} 37 | PlasmPrefix = []byte{0x05} 38 | BifrostPrefix = []byte{0x06} 39 | EdgewarePrefix = []byte{0x07} 40 | KaruraPrefix = []byte{0x08} 41 | ReynoldsPrefix = []byte{0x09} 42 | AcalaPrefix = []byte{0x0a} 43 | LaminarPrefix = []byte{0x0b} 44 | PolymathPrefix = []byte{0x0c} 45 | SubstraTEEPrefix = []byte{0x0d} 46 | KulupuPrefix = []byte{0x10} 47 | DarkPrefix = []byte{0x11} 48 | DarwiniaPrefix = []byte{0x12} 49 | StafiPrefix = []byte{0x14} 50 | DockTestNetPrefix = []byte{0x15} 51 | DockMainNetPrefix = []byte{0x16} 52 | ShiftNrgPrefix = []byte{0x17} 53 | SubsocialPrefix = []byte{0x1c} 54 | PhalaPrefix = []byte{0x1e} 55 | RobonomicsPrefix = []byte{0x20} 56 | DataHighwayPrefix = []byte{0x21} 57 | CentrifugePrefix = []byte{0x24} 58 | MathMainPrefix = []byte{0x27} 59 | MathTestPrefix = []byte{0x28} 60 | SubstratePrefix = []byte{0x2a} 61 | ChainXPrefix = []byte{0x2c} 62 | ChainCessTestPrefix = []byte{0x50, 0xac} 63 | ) 64 | 65 | type Bar struct { 66 | percent int64 67 | cur int64 68 | total int64 69 | rate string 70 | graph string 71 | calibrate float64 72 | } 73 | 74 | func Post(url string, para interface{}) ([]byte, error) { 75 | body, err := json.Marshal(para) 76 | if err != nil { 77 | return nil, err 78 | } 79 | req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) 80 | var resp = new(http.Response) 81 | resp, err = http.DefaultClient.Do(req) 82 | if err != nil { 83 | return nil, err 84 | } 85 | if resp != nil { 86 | respBody, err := ioutil.ReadAll(resp.Body) 87 | if err != nil { 88 | return nil, err 89 | } 90 | return respBody, err 91 | } 92 | return nil, err 93 | } 94 | 95 | func PostFileChunks(url, filepath string, params map[string]string) (status int, err error) { 96 | r, w := io.Pipe() 97 | m := multipart.NewWriter(w) 98 | //for key, val := range params { 99 | // m.WriteField(key, val) 100 | //} 101 | go func() { 102 | defer w.Close() 103 | defer m.Close() 104 | file, err := os.Open(filepath) 105 | if err != nil { 106 | fmt.Printf("Fail to open the file,error:%s", err) 107 | return 108 | } 109 | part, err := m.CreateFormFile("file", params["file"]) 110 | if err != nil { 111 | fmt.Printf("Failed to create form file,error:%s", err) 112 | return 113 | } 114 | for key, val := range params { 115 | m.WriteField(key, val) 116 | } 117 | defer file.Close() 118 | if _, err = io.Copy(part, file); err != nil { 119 | fmt.Printf("Failed to send file chunks,error:%s\n", err) 120 | return 121 | } 122 | }() 123 | resp, err := http.Post(url, m.FormDataContentType(), r) 124 | if err != nil { 125 | return resp.StatusCode, err 126 | } 127 | return 128 | } 129 | 130 | func PostFile(url string, filepath string, params map[string]string) (status int, err error) { 131 | body := new(bytes.Buffer) 132 | writer := multipart.NewWriter(body) 133 | part, err := writer.CreateFormFile("file", params["file"]) 134 | if err != nil { 135 | return 0, err 136 | } 137 | src, err := os.Open(filepath) 138 | if err != nil { 139 | return 0, err 140 | } 141 | defer src.Close() 142 | 143 | _, err = io.Copy(part, src) 144 | if err != nil { 145 | return 0, err 146 | } 147 | for key, val := range params { 148 | writer.WriteField(key, val) 149 | } 150 | err = writer.Close() 151 | if err != nil { 152 | return 0, err 153 | } 154 | request, err := http.NewRequest("POST", url, body) 155 | if err != nil { 156 | return 0, err 157 | } 158 | request.Header.Add("Content-Type", writer.FormDataContentType()) 159 | client := &http.Client{} 160 | resp, err := client.Do(request) 161 | if err != nil { 162 | return 0, err 163 | } 164 | defer resp.Body.Close() 165 | // content, err := ioutil.ReadAll(resp.Body) 166 | // if err != nil { 167 | // logger.ErrLogger.Sugar().Errorf("%v", err) 168 | // return err 169 | // } 170 | // fmt.Println(string(content)) 171 | return resp.StatusCode, err 172 | } 173 | 174 | func CalcFileHash(filepath string) (string, error) { 175 | f, err := os.Open(filepath) 176 | if err != nil { 177 | return "", err 178 | } 179 | defer f.Close() 180 | 181 | h := sha256.New() 182 | if _, err := io.Copy(h, f); err != nil { 183 | return "", err 184 | } 185 | return hex.EncodeToString(h.Sum(nil)), nil 186 | } 187 | 188 | //Get file unique identifier 189 | func GetGuid(num int64) (string, error) { 190 | node, err := snowflake.NewNode(num) 191 | if err != nil { 192 | return "", err 193 | } 194 | 195 | id := node.Generate() 196 | return id.String(), nil 197 | } 198 | 199 | func (bar *Bar) NewOption(start, total int64) { 200 | bar.cur = start 201 | bar.total = total 202 | if bar.graph == "" { 203 | bar.graph = "█" 204 | } 205 | bar.percent = bar.getPercent() 206 | for i := 0; i < int(bar.percent); i += 2 { 207 | bar.rate += bar.graph 208 | } 209 | } 210 | 211 | func (bar *Bar) getPercent() int64 { 212 | return int64(float32(bar.cur) / float32(bar.total) * 100) 213 | } 214 | 215 | func (bar *Bar) Play(cur int64) { 216 | bar.cur = cur 217 | last := bar.percent 218 | bar.percent = bar.getPercent() 219 | if bar.percent != last { 220 | add := (float64(int(bar.percent-last)) / 100) * 50 221 | bar.calibrate += add - float64(int(add)) 222 | for i := 0; i < int(add); i++ { 223 | bar.rate += bar.graph 224 | } 225 | if int(bar.calibrate) > 0 { 226 | for i := 0; i < int(bar.calibrate); i++ { 227 | bar.rate += bar.graph 228 | } 229 | bar.calibrate = 0 230 | } 231 | } 232 | fmt.Printf("\r[%-50s]%3d%% %8d/%d", bar.rate, bar.percent, bar.cur, bar.total) 233 | } 234 | func (bar *Bar) Finish() { 235 | fmt.Println() 236 | } 237 | 238 | //prefix: chain.SubstratePrefix 239 | func EncodeByPubHex(publicHex string, prefix []byte) (string, error) { 240 | publicKeyHash, err := hex.DecodeString(publicHex) 241 | if err != nil { 242 | return "", err 243 | } 244 | return Encode(publicKeyHash, prefix) 245 | } 246 | 247 | func DecodeToPub(address string, prefix []byte) ([]byte, error) { 248 | err := VerityAddress(address, prefix) 249 | if err != nil { 250 | return nil, errors.New("Invalid addrss") 251 | } 252 | data := base58.Decode(address) 253 | if len(data) != (34 + len(prefix)) { 254 | return nil, errors.New("base58 decode error") 255 | } 256 | return data[len(prefix) : len(data)-2], nil 257 | } 258 | 259 | func PubBytesTo0XString(pub []byte) string { 260 | return fmt.Sprintf("%#x", pub) 261 | } 262 | 263 | func PubBytesToString(b []byte) string { 264 | s := "" 265 | for i := 0; i < len(b); i++ { 266 | tmp := fmt.Sprintf("%#02x", b[i]) 267 | s += tmp[2:] 268 | } 269 | return s 270 | } 271 | 272 | func Encode(publicKeyHash []byte, prefix []byte) (string, error) { 273 | if len(publicKeyHash) != 32 { 274 | return "", errors.New("public hash length is not equal 32") 275 | } 276 | payload := appendBytes(prefix, publicKeyHash) 277 | input := appendBytes(SSPrefix, payload) 278 | ck := blake2b.Sum512(input) 279 | checkum := ck[:2] 280 | address := base58.Encode(appendBytes(payload, checkum)) 281 | if address == "" { 282 | return address, errors.New("base58 encode error") 283 | } 284 | return address, nil 285 | } 286 | 287 | func appendBytes(data1, data2 []byte) []byte { 288 | if data2 == nil { 289 | return data1 290 | } 291 | return append(data1, data2...) 292 | } 293 | 294 | func VerityAddress(address string, prefix []byte) error { 295 | decodeBytes := base58.Decode(address) 296 | if len(decodeBytes) != (34 + len(prefix)) { 297 | return errors.New("base58 decode error") 298 | } 299 | if decodeBytes[0] != prefix[0] { 300 | return errors.New("prefix valid error") 301 | } 302 | pub := decodeBytes[len(prefix) : len(decodeBytes)-2] 303 | 304 | data := append(prefix, pub...) 305 | input := append(SSPrefix, data...) 306 | ck := blake2b.Sum512(input) 307 | checkSum := ck[:2] 308 | for i := 0; i < 2; i++ { 309 | if checkSum[i] != decodeBytes[32+len(prefix)+i] { 310 | return errors.New("checksum valid error") 311 | } 312 | } 313 | if len(pub) != 32 { 314 | return errors.New("decode public key length is not equal 32") 315 | } 316 | return nil 317 | } 318 | --------------------------------------------------------------------------------