├── .gitignore ├── INSTALLATION.md ├── LICENSE ├── README.md ├── config.sample.mainnet.json ├── config └── config.go ├── explorer ├── init.go ├── scan.go └── script.go ├── go.mod ├── go.sum ├── main.go ├── misc ├── address.go └── batch.go ├── operation ├── init.go ├── op_blacklist.go ├── op_burn.go ├── op_chown.go ├── op_deploy.go ├── op_issue.go ├── op_list.go ├── op_mint.go ├── op_send.go └── op_transfer.go ├── protowire ├── message.pb.go ├── message.proto ├── message_grpc.pb.go ├── rpc.pb.go └── rpc.proto ├── sf-class2-root.crt └── storage ├── cassa.go ├── cql.go ├── init.go ├── node.go ├── rocks.go ├── runtime.go ├── state.go └── type.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /config.json 3 | /main 4 | /kpexecutor -------------------------------------------------------------------------------- /INSTALLATION.md: -------------------------------------------------------------------------------- 1 | # Kasplex indexer-executor installation 2 | 3 | **Note:** All commands in this document were executed on Linux Ubuntu. 4 | 5 | ## 1. Install dependencies that might be needed. 6 | ```shell 7 | sudo apt install -y libsnappy-dev libgflags-dev unzip make build-essential 8 | ``` 9 | 10 | ## 2. Install Rocksdb 11 | 12 | 2.1 download rocksdb of version: 6.15.5 13 | ```shell 14 | cd /var 15 | sudo wget https://github.com/facebook/rocksdb/archive/refs/tags/v6.15.5.zip -O ./rocksdb.6155.zip 16 | ``` 17 | 18 | 2.2 unzip the rocksdb 19 | ```shell 20 | sudo unzip -o -d /var ./rocksdb.6155.zip 21 | ``` 22 | 23 | 2.3 make the rocksdb 24 | ```shell 25 | cd /var/rocksdb-6.15.5 26 | sudo PORTABLE=1 make shared_lib 27 | sudo INSTALL_PATH=/usr/lib make install-shared 28 | ``` 29 | 30 | 31 | ## 3. Install Golang 32 | 3.1 Here we recommend that you install the latest version of Golang 33 | ```shell 34 | sudo snap install go --classic 35 | ``` 36 | 37 | 3.2 If you need to install a specific version of Golang, you can use the following command: 38 | Replace with the desired version number, for example: 39 | ```shell 40 | sudo snap install go --channel= 41 | ``` 42 | 43 | ## 4. Project Initialization 44 | 45 | 4.1 Download code from github 46 | ```shell 47 | git clone https://github.com/kasplex/indexer-executor.git 48 | ``` 49 | 50 | 4.2 build the program of executor 51 | ```shell 52 | cd indexer-executor 53 | go get -u && go mod tidy 54 | go build -o kpexecutor main.go 55 | ``` 56 | 57 | ## 5. Run Project 58 | 59 | 5.1 Create a configuration file named config.json. Use config.sample.mainnet.json as a reference. The configuration details are as follows: 60 | ```shell 61 | { 62 | "startup": { // executor job config 63 | "hysteresis": 3, // hysteresis indicates the number of blocks delayed from the latest block, with a suggested value of 3. A setting of 0 means no lag, providing the highest real-time performance but is more prone to handling higher numbers of rollbacks. 64 | "start": "", // the hash of the sync start block, only when testnet=true. 65 | "daaScoreRange": [], // just The range for executing daascore, only when testnet=true. 66 | "tickReserved": [] // The reserved tick address list, only when testnet=true. 67 | }, 68 | "cassandra": { // cassandra config 69 | "host": "", // connection host 70 | "port": 9142, // connection port 71 | "user": "", // connection user 72 | "pass": "", // connection password 73 | "crt": "/path/****.crt", // file path of db access key 74 | "space": "" // connection name 75 | }, 76 | "rocksdb": { // This part is the local database parameters. 77 | "path": "./data" // db path 78 | }, 79 | "testnet": false, //true: testnet false: mainnet 80 | "debug": 2 //log level: 1:Warn 2: Info 3:Debug 81 | } 82 | ``` 83 | 84 | 5.2 Run manually. 85 | ```shell 86 | sudo ./kpexecutor 87 | ``` 88 | 89 | ## 6. Configure system services (optional). 90 | 91 | 6.1 Create the /etc/systemd/system/kasplex-executor.service file, and set the content as below, remember to replace the correct project path inside. 92 | ```shell 93 | [Unit] 94 | Description=Kasplex Executor Service 95 | After=network.target 96 | [Service] 97 | WorkingDirectory=/path/to/your/project 98 | ExecStart=/path/to/your/project/kpexecutor 99 | Restart=always 100 | RestartSec=10s 101 | User=root 102 | Group=root 103 | [Install] 104 | WantedBy=multi-user.target 105 | ``` 106 | 107 | 6.2 Update service configuration. 108 | ```shell 109 | sudo systemctl daemon-reload 110 | sudo systemctl enable kasplex-executor.service 111 | ``` 112 | 113 | 6.3 Start service. 114 | ```shell 115 | sudo systemctl start kasplex-executor.service 116 | ``` 117 | 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Kasplex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kasplex indexer-executor 2 | 3 | # 1. Description 4 | Executor's main responsibilities include but are not limited to: 5 | 6 | ### 1.1 Generating and Parsing Data 7 | 8 | - **Token Balances**: Converting users' token balances into an intuitive format, making it easy for users to view and manage their assets. 9 | - **User Address Information**: Parsing user address information to ensure that each user's identity is correctly identified and recorded. 10 | - **Transaction (TX) Data**: Extracting key information from transaction data, such as transaction amounts, senders, and recipients, and presenting it in a clear manner to the user. 11 | 12 | ### 1.2 Data Algorithm Processing 13 | 14 | - Executor utilizes advanced data algorithms to process transaction data, ensuring that every transaction is accurate. 15 | - By performing in-depth analysis of transaction data, Executor guarantees the consistency and integrity of the data, thereby enhancing users' trust in the system. 16 | 17 | ### 1.3 Ensuring Data Accuracy and Security 18 | 19 | - Executor focuses not only on data accuracy but also on data security. 20 | - It employs encryption technologies and security protocols to protect user data, preventing data breaches and tampering. 21 | 22 | Through these functionalities, Executor aims to provide users with a highly efficient, accurate, and secure blockchain experience. 23 | 24 | # 2. Installation 25 | For installation instructions, please see our [Installation Guide](INSTALLATION.md). -------------------------------------------------------------------------------- /config.sample.mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "startup": { 3 | "hysteresis": 3, 4 | "daaScoreRange": [], 5 | "tickReserved": [] 6 | }, 7 | "cassandra": { 8 | "host": "", 9 | "port": 9142, 10 | "user": "", 11 | "pass": "", 12 | "crt": "sf-class2-root.crt", 13 | "space": "kpdb01mainnet" 14 | }, 15 | "rocksdb": { 16 | "path": "./data" 17 | }, 18 | "testnet": false, 19 | "debug": 2 20 | } 21 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package config 4 | 5 | import ( 6 | "os" 7 | "log" 8 | "encoding/json" 9 | ) 10 | 11 | //////////////////////////////// 12 | type StartupConfig struct { 13 | Hysteresis int `json:"hysteresis"` 14 | DaaScoreRange [][2]uint64 `json:"daaScoreRange"` 15 | TickReserved []string `json:"tickReserved"` 16 | } 17 | type CassaConfig struct { 18 | Host string `json:"host"` 19 | Port int `json:"port"` 20 | User string `json:"user"` 21 | Pass string `json:"pass"` 22 | Crt string `json:"crt"` 23 | Space string `json:"space"` 24 | } 25 | type RocksConfig struct { 26 | Path string `json:"path"` 27 | } 28 | type Config struct { 29 | Startup StartupConfig `json:"startup"` 30 | Cassandra CassaConfig `json:"cassandra"` 31 | Rocksdb RocksConfig `json:"rocksdb"` 32 | Debug int `json:"debug"` 33 | Testnet bool `json:"testnet"` 34 | } 35 | 36 | //////////////////////////////// 37 | const Version = "2.03.250415" 38 | 39 | //////////////////////////////// 40 | func Load(cfg *Config) { 41 | 42 | // File "config.json" should be in the same directory. 43 | 44 | dir, _ := os.Getwd() 45 | fp, err := os.Open(dir + "/config.json") 46 | if err != nil { 47 | log.Fatalln("config.Load fatal:", err.Error()) 48 | } 49 | defer fp.Close() 50 | jParser := json.NewDecoder(fp) 51 | err = jParser.Decode(&cfg) 52 | if err != nil { 53 | log.Fatalln("config.Load fatal:", err.Error()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /explorer/init.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package explorer 4 | 5 | import ( 6 | "sync" 7 | "context" 8 | "time" 9 | "log" 10 | "log/slog" 11 | "kasplex-executor/config" 12 | "kasplex-executor/storage" 13 | "kasplex-executor/operation" 14 | ) 15 | 16 | //////////////////////////////// 17 | type runtimeType struct { 18 | ctx context.Context 19 | wg *sync.WaitGroup 20 | cfg config.StartupConfig 21 | vspcList []storage.DataVspcType 22 | rollbackList []storage.DataRollbackType 23 | opScoreLast uint64 24 | synced bool 25 | testnet bool 26 | } 27 | var eRuntime runtimeType 28 | 29 | // Available daaScore range. 30 | var daaScoreRange = [][2]uint64{ 31 | {83441551, 83525600}, 32 | {90090600, 18446744073709551615}, 33 | } 34 | 35 | //////////////////////////////// 36 | func Init(ctx context.Context, wg *sync.WaitGroup, cfg config.StartupConfig, testnet bool) { 37 | slog.Info("explorer.Init start.") 38 | var err error 39 | eRuntime.synced = false 40 | eRuntime.ctx = ctx 41 | eRuntime.wg = wg 42 | eRuntime.cfg = cfg 43 | eRuntime.testnet = testnet 44 | if eRuntime.cfg.Hysteresis < 0 { 45 | eRuntime.cfg.Hysteresis = 0 46 | } else if eRuntime.cfg.Hysteresis > 1000 { 47 | eRuntime.cfg.Hysteresis = 1000 48 | } 49 | if (!testnet || len(eRuntime.cfg.DaaScoreRange) <= 0) { 50 | eRuntime.cfg.DaaScoreRange = daaScoreRange 51 | } 52 | if (testnet && len(eRuntime.cfg.TickReserved) > 0) { 53 | operation.ApplyTickReserved(eRuntime.cfg.TickReserved) 54 | } 55 | eRuntime.rollbackList, err = storage.GetRuntimeRollbackLast() 56 | if err != nil { 57 | log.Fatalln("explorer.Init fatal:", err.Error()) 58 | } 59 | eRuntime.vspcList, err = storage.GetRuntimeVspcLast() 60 | if err != nil { 61 | log.Fatalln("explorer.Init fatal:", err.Error()) 62 | } 63 | indexRollback := len(eRuntime.rollbackList) - 1 64 | if indexRollback >= 0 { 65 | eRuntime.opScoreLast = eRuntime.rollbackList[indexRollback].OpScoreLast 66 | } 67 | if len(eRuntime.vspcList) > 0 { 68 | lenVspc := len(eRuntime.vspcList) 69 | vspcLast := eRuntime.vspcList[lenVspc-1] 70 | slog.Info("explorer.Init", "lastVspcDaaScore", vspcLast.DaaScore, "lastVspcBlockHash", vspcLast.Hash) 71 | storage.SetRuntimeSynced(false, eRuntime.opScoreLast, vspcLast.DaaScore) 72 | } else { 73 | slog.Info("explorer.Init", "lastVspcDaaScore", eRuntime.cfg.DaaScoreRange[0][0], "lastVspcBlockHash", "") 74 | storage.SetRuntimeSynced(false, eRuntime.opScoreLast, eRuntime.cfg.DaaScoreRange[0][0]) 75 | } 76 | storage.SetRuntimeVersion(config.Version) 77 | slog.Info("explorer ready.") 78 | } 79 | 80 | //////////////////////////////// 81 | func Run() { 82 | eRuntime.wg.Add(1) 83 | defer eRuntime.wg.Done() 84 | loop: 85 | for { 86 | select { 87 | case <-eRuntime.ctx.Done(): 88 | slog.Info("explorer.Scan stopped.") 89 | break loop 90 | default: 91 | scan() 92 | // Basic loop delay. 93 | time.Sleep(100*time.Millisecond) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /explorer/scan.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package explorer 4 | 5 | import ( 6 | "time" 7 | "strconv" 8 | "log/slog" 9 | "kasplex-executor/storage" 10 | "kasplex-executor/operation" 11 | ) 12 | 13 | //////////////////////////////// 14 | const lenVspcListMax = 1200 15 | const lenVspcListRuntimeMax = 3600 16 | const lenVspcCheck = 200 17 | const lenRollbackListRuntimeMax = 3600 18 | 19 | //////////////////////////////// 20 | var lenVspcListMaxAdj = lenVspcListMax 21 | var lenVspcBatch = uint64(lenVspcListMax - lenVspcCheck) 22 | 23 | //////////////////////////////// 24 | func scan() { 25 | mtss := time.Now().UnixMilli() 26 | 27 | // Get the next vspc data list. 28 | vspcLast := storage.DataVspcType{ 29 | DaaScore: eRuntime.cfg.DaaScoreRange[0][0], 30 | } 31 | daaScoreStart := vspcLast.DaaScore 32 | // Use the last vspc if not empty list. 33 | lenVspcRuntime := len(eRuntime.vspcList) 34 | if lenVspcRuntime > 0 { 35 | vspcLast = eRuntime.vspcList[lenVspcRuntime-1] 36 | daaScoreStart = vspcLast.DaaScore - lenVspcCheck 37 | if daaScoreStart < eRuntime.vspcList[0].DaaScore { 38 | daaScoreStart = eRuntime.vspcList[0].DaaScore 39 | } 40 | } 41 | //_, daaScoreStart = checkDaaScoreRange(daaScoreStart) 42 | // Get next vspc data list from cluster db. 43 | vspcListNext, mtsBatchVspc, err := storage.GetNodeVspcList(daaScoreStart, lenVspcListMaxAdj+5) 44 | if err != nil { 45 | slog.Warn("storage.GetNodeVspcList failed, sleep 3s.", "daaScore", daaScoreStart, "error", err.Error()) 46 | time.Sleep(3000*time.Millisecond) 47 | return 48 | } 49 | // Ignore the last reserved vspc data if synced, reduce the probability of vspc-reorg. 50 | lenVspcNext := len(vspcListNext) 51 | if (eRuntime.synced) { 52 | lenVspcNext -= eRuntime.cfg.Hysteresis 53 | } 54 | if lenVspcNext <= 0 { 55 | slog.Debug("storage.GetNodeVspcList empty.", "daaScore", daaScoreStart) 56 | time.Sleep(1550*time.Millisecond) 57 | return 58 | } 59 | vspcListNext = vspcListNext[:lenVspcNext] 60 | slog.Info("storage.GetNodeVspcList", "daaScore", daaScoreStart, "lenBlock/mSecond", strconv.Itoa(lenVspcNext)+"/"+strconv.Itoa(int(mtsBatchVspc)), "lenVspcListMax", lenVspcListMaxAdj, "synced", eRuntime.synced) 61 | 62 | // Check vspc list if need rollback. 63 | daaScoreRollback, vspcListNext := checkRollbackNext(eRuntime.vspcList, vspcListNext, daaScoreStart) 64 | if daaScoreRollback > 0 { 65 | daaScoreLast := uint64(0) 66 | mtsRollback := int64(0) 67 | // Rollback to the last state data batch. 68 | lenRollback := len(eRuntime.rollbackList) - 1 69 | if (lenRollback >= 0 && eRuntime.rollbackList[lenRollback].DaaScoreEnd >= daaScoreRollback) { 70 | daaScoreLast = eRuntime.rollbackList[lenRollback].DaaScoreStart 71 | mtsRollback, err = storage.RollbackOpStateBatch(eRuntime.rollbackList[lenRollback]) 72 | if err != nil { 73 | slog.Warn("storage.RollbackOpStateBatch failed, sleep 3s.", "error", err.Error()) 74 | time.Sleep(3000*time.Millisecond) 75 | return 76 | } 77 | // Remove the vspc data of rollback. 78 | for { 79 | lenVspcRuntime = len(eRuntime.vspcList) 80 | if lenVspcRuntime <= 0 { 81 | break 82 | } 83 | lenVspcRuntime -- 84 | if eRuntime.vspcList[lenVspcRuntime].DaaScore >= daaScoreLast { 85 | if lenVspcRuntime == 0 { 86 | eRuntime.vspcList = []storage.DataVspcType{} 87 | break 88 | } 89 | eRuntime.vspcList = eRuntime.vspcList[:lenVspcRuntime] 90 | continue 91 | } 92 | break 93 | } 94 | // Remove the last rollback data. 95 | eRuntime.rollbackList = eRuntime.rollbackList[:lenRollback] 96 | storage.SetRuntimeRollbackLast(eRuntime.rollbackList) 97 | } else { 98 | eRuntime.vspcList = vspcListNext 99 | } 100 | storage.SetRuntimeVspcLast(eRuntime.vspcList) 101 | slog.Info("explorer.checkRollbackNext", "start/rollback/last", strconv.FormatUint(daaScoreStart,10)+"/"+strconv.FormatUint(daaScoreRollback,10)+"/"+strconv.FormatUint(daaScoreLast,10), "mSecond", strconv.Itoa(int(mtsRollback))) 102 | return 103 | } else if vspcListNext == nil { 104 | lenVspcListMaxAdj += 50 105 | if lenVspcListMaxAdj > lenVspcListRuntimeMax { 106 | lenVspcListMaxAdj = lenVspcListRuntimeMax 107 | } 108 | slog.Debug("storage.checkDaaScoreRollback empty.", "daaScore", daaScoreStart, "lenVspcListMax", lenVspcListMaxAdj) 109 | eRuntime.synced = false 110 | time.Sleep(1750*time.Millisecond) 111 | return 112 | } 113 | lenVspcListMaxAdj = lenVspcListMax 114 | lenVspcNext = len(vspcListNext) 115 | slog.Debug("explorer.checkRollbackNext", "start/next", strconv.FormatUint(daaScoreStart,10)+"/"+strconv.FormatUint(vspcListNext[0].DaaScore,10)) 116 | 117 | // Extract and get the transaction list. 118 | daaScoreNextBatch := uint64(0) 119 | vspcRemoveIndex := 0 120 | txDataList := []storage.DataTransactionType{} 121 | for i, vspc := range vspcListNext { 122 | if vspc.DaaScore <= vspcLast.DaaScore { 123 | continue 124 | } 125 | if daaScoreNextBatch == 0 { 126 | daaScoreNextBatch = (vspc.DaaScore/lenVspcBatch+1) * lenVspcBatch 127 | } else if vspc.DaaScore >= daaScoreNextBatch { 128 | vspcRemoveIndex = i 129 | break 130 | } 131 | passed, _ := checkDaaScoreRange(vspc.DaaScore) 132 | if !passed { 133 | continue 134 | } 135 | for _, txId := range vspc.TxIdList { 136 | txDataList = append(txDataList, storage.DataTransactionType{ 137 | TxId: txId, 138 | DaaScore: vspc.DaaScore, 139 | BlockAccept: vspc.Hash, 140 | }) 141 | } 142 | } 143 | if vspcRemoveIndex > 0 { 144 | vspcListNext = vspcListNext[:vspcRemoveIndex] 145 | } 146 | lenVspcNext = len(vspcListNext) 147 | // Get the transaction data list from cluster db. 148 | lenTxData := len(txDataList) 149 | txDataList, mtsBatchTx, err := storage.GetNodeTransactionDataList(txDataList) 150 | if err != nil { 151 | slog.Warn("storage.GetNodeTransactionDataList failed, sleep 3s.", "lenTransaction", lenTxData, "error", err.Error()) 152 | time.Sleep(3000*time.Millisecond) 153 | return 154 | } 155 | slog.Info("storage.GetNodeTransactionDataList", "lenTransaction/mSecond", strconv.Itoa(lenTxData)+"/"+strconv.Itoa(int(mtsBatchTx))) 156 | 157 | // Parse the transaction and calculate fee for OP. 158 | opDataList, mtsBatchOp, err := ParseOpDataList(txDataList) 159 | if err != nil { 160 | slog.Warn("explorer.ParseOpDataList failed, sleep 3s.", "error", err.Error()) 161 | time.Sleep(3000*time.Millisecond) 162 | return 163 | } 164 | lenOpData := len(opDataList) 165 | slog.Info("explorer.ParseOpDataList", "lenOperation/mSecond", strconv.Itoa(lenOpData)+"/"+strconv.Itoa(int(mtsBatchOp))) 166 | 167 | // Prepare the op data list. 168 | stateMap, mtsBatchSt, err := operation.PrepareStateBatch(opDataList) 169 | if err != nil { 170 | slog.Warn("operation.PrepareStateBatch failed, sleep 3s.", "error", err.Error()) 171 | time.Sleep(3000*time.Millisecond) 172 | return 173 | } 174 | slog.Debug("operation.PrepareStateBatch", "lenToken/lenBalance/mSecond", strconv.Itoa(len(stateMap.StateTokenMap))+"/"+strconv.Itoa(len(stateMap.StateBalanceMap))+"/"+strconv.Itoa(int(mtsBatchSt))) 175 | 176 | // Execute the op list and generate the rollback data. 177 | checkpointLast := "" 178 | if len(eRuntime.rollbackList) > 0 { 179 | checkpointLast = eRuntime.rollbackList[len(eRuntime.rollbackList)-1].CheckpointAfter 180 | } 181 | rollback, mtsBatchExe, err := operation.ExecuteBatch(opDataList, stateMap, checkpointLast, eRuntime.testnet) 182 | if err != nil { 183 | slog.Warn("operation.ExecuteBatch failed, sleep 3s.", "error", err.Error()) 184 | time.Sleep(3000*time.Millisecond) 185 | return 186 | } 187 | rollback.DaaScoreStart = vspcListNext[0].DaaScore 188 | rollback.DaaScoreEnd = vspcListNext[lenVspcNext-1].DaaScore 189 | if rollback.CheckpointAfter == "" { 190 | rollback.CheckpointAfter = rollback.CheckpointBefore 191 | } 192 | if rollback.OpScoreLast == 0 { 193 | rollback.OpScoreLast = eRuntime.opScoreLast 194 | } else { 195 | eRuntime.opScoreLast = rollback.OpScoreLast 196 | } 197 | slog.Debug("operation.ExecuteBatch", "checkpoint", rollback.CheckpointAfter, "lenOperation/mSecond", strconv.Itoa(lenOpData)+"/"+strconv.Itoa(int(mtsBatchExe))) 198 | 199 | // Save the op/state result data list. 200 | mtsBatchList, err := storage.SaveOpStateBatch(opDataList, stateMap) 201 | if err != nil { 202 | slog.Warn("storage.SaveOpStateBatch failed, sleep 3s.", "error", err.Error()) 203 | time.Sleep(3000*time.Millisecond) 204 | return 205 | } 206 | slog.Debug("operation.SaveOpStateBatch", "mSecondList", strconv.Itoa(int(mtsBatchList[0]))+"/"+strconv.Itoa(int(mtsBatchList[1]))+"/"+strconv.Itoa(int(mtsBatchList[2]))+"/"+strconv.Itoa(int(mtsBatchList[3]))) 207 | 208 | // Update the runtime data. 209 | eRuntime.synced = false 210 | if (lenVspcNext < 99) { 211 | eRuntime.synced = true 212 | } 213 | storage.SetRuntimeSynced(eRuntime.synced, eRuntime.opScoreLast, vspcListNext[lenVspcNext-1].DaaScore) 214 | eRuntime.vspcList = append(eRuntime.vspcList, vspcListNext...) 215 | lenStart := len(eRuntime.vspcList) - lenVspcListRuntimeMax 216 | if lenStart > 0 { 217 | eRuntime.vspcList = eRuntime.vspcList[lenStart:] 218 | } 219 | storage.SetRuntimeVspcLast(eRuntime.vspcList) 220 | eRuntime.rollbackList = append(eRuntime.rollbackList, rollback) 221 | lenStart = 0 222 | lenRollback := len(eRuntime.rollbackList) 223 | for i := lenRollback-1; i >= 0; i -- { 224 | if rollback.DaaScoreEnd - eRuntime.rollbackList[i].DaaScoreStart >= lenRollbackListRuntimeMax { 225 | lenStart = i 226 | break 227 | } 228 | } 229 | if lenStart > 0 { 230 | eRuntime.rollbackList = eRuntime.rollbackList[lenStart:] 231 | } 232 | storage.SetRuntimeRollbackLast(eRuntime.rollbackList) 233 | 234 | // Additional delay if state synced. 235 | mtsLoop := time.Now().UnixMilli() - mtss 236 | slog.Info("explorer.scan", "lenRuntimeVspc", len(eRuntime.vspcList), "lenRuntimeRollback", len(eRuntime.rollbackList), "lenOperation", lenOpData, "mSecondLoop", mtsLoop) 237 | if (eRuntime.synced) { 238 | mtsLoop = 850 - mtsLoop 239 | if mtsLoop <=0 { 240 | return 241 | } 242 | time.Sleep(time.Duration(mtsLoop)*time.Millisecond) 243 | } 244 | } 245 | 246 | //////////////////////////////// 247 | func checkRollbackNext(vspcListPrev []storage.DataVspcType, vspcListNext []storage.DataVspcType, daaScoreStart uint64) (uint64, []storage.DataVspcType) { 248 | if len(vspcListPrev) <= 0 { 249 | return 0, vspcListNext 250 | } 251 | vspcList1 := []storage.DataVspcType{} 252 | vspcList2 := []storage.DataVspcType{} 253 | for _, vspc := range vspcListPrev { 254 | if vspc.DaaScore < daaScoreStart { 255 | continue 256 | } 257 | vspcList1 = append(vspcList1, vspc) 258 | } 259 | lenCheck := len(vspcList1) 260 | if lenCheck > 0 { 261 | if len(vspcListNext) <= lenCheck { 262 | return 0, nil 263 | } else { 264 | vspcList2 = vspcListNext[:lenCheck] 265 | } 266 | } else { 267 | return 0, vspcListNext 268 | } 269 | for i := 0; i < lenCheck; i ++ { 270 | if (vspcList1[i].DaaScore != vspcList2[i].DaaScore || vspcList1[i].Hash != vspcList2[i].Hash) { 271 | return vspcList1[i].DaaScore, vspcListPrev[:(len(vspcListPrev)-lenCheck+i)] 272 | } 273 | } 274 | return 0, vspcListNext[lenCheck:] 275 | } 276 | 277 | //////////////////////////////// 278 | func checkDaaScoreRange(daaScore uint64) (bool, uint64) { 279 | for _, dRange := range eRuntime.cfg.DaaScoreRange { 280 | if daaScore < dRange[0] { 281 | return false, dRange[0] 282 | } else if (daaScore <= dRange[1]) { 283 | return true, daaScore 284 | } 285 | } 286 | return false, daaScore 287 | } 288 | -------------------------------------------------------------------------------- /explorer/script.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package explorer 4 | 5 | import ( 6 | "time" 7 | "sync" 8 | "strconv" 9 | "strings" 10 | "unicode" 11 | //"log/slog" 12 | "encoding/hex" 13 | "encoding/json" 14 | "kasplex-executor/misc" 15 | "kasplex-executor/storage" 16 | "kasplex-executor/operation" 17 | ) 18 | 19 | //////////////////////////////// 20 | // Parse the P2SH transaction input script. 21 | func parseScriptInput(script string) (bool, []string) { 22 | script = strings.ToLower(script) 23 | lenScript := len(script) 24 | if (lenScript <= 138) { 25 | return false, nil 26 | } 27 | // Get the next data length and position. 28 | _lGet := func(s string, i int) (int64, int, bool) { 29 | iRaw := i 30 | lenS := len(s) 31 | if lenS < (i + 2) { 32 | return 0, iRaw, false 33 | } 34 | f := s[i:i+2] 35 | i += 2 36 | lenD := int64(0) 37 | if f == "4c" { 38 | if lenS < (i + 2) { 39 | return 0, iRaw, false 40 | } 41 | f := s[i:i+2] 42 | i += 2 43 | lenD, _ = strconv.ParseInt(f, 16, 32) 44 | } else if f == "4d" { 45 | if lenS < (i + 4) { 46 | return 0, iRaw, false 47 | } 48 | f := s[i+2:i+4] + s[i:i+2] 49 | i += 4 50 | lenD, _ = strconv.ParseInt(f, 16, 32) 51 | } else { 52 | lenD, _ = strconv.ParseInt(f, 16, 32) 53 | if (lenD <0 || lenD > 75) { 54 | return 0, iRaw, false 55 | } 56 | } 57 | lenD *= 2 58 | return lenD, i, true 59 | } 60 | 61 | // Get the push number and position. 62 | _nGet := func(s string, i int) (int64, int, bool) { 63 | iRaw := i 64 | lenS := len(s) 65 | if lenS < (i + 2) { 66 | return 0, iRaw, false 67 | } 68 | f := s[i:i+2] 69 | i += 2 70 | num, _ := strconv.ParseInt(f, 16, 32) 71 | if (num < 81 || num > 96) { 72 | return 0, iRaw, false 73 | } 74 | num -= 80 75 | return num, i, true 76 | } 77 | 78 | // Get the last data position. 79 | _dGotoLast := func(s string, i int) (int, bool) { 80 | iRaw := i 81 | lenS := len(s) 82 | lenD := int64(0) 83 | r := true 84 | for j := 0; j < 16; j ++ { 85 | lenD, i, r = _lGet(s, i) 86 | if !r { 87 | return iRaw, false 88 | } 89 | if lenS < (i + int(lenD)) { 90 | return iRaw, false 91 | } else if lenS == (i + int(lenD)) { 92 | if lenD < 94 { 93 | return iRaw, false 94 | } 95 | return i, true 96 | } else { 97 | i += int(lenD) 98 | } 99 | } 100 | return iRaw, false 101 | } 102 | 103 | // Skip to the redeem script. 104 | r := true 105 | n := 0 106 | flag := "" 107 | n, r = _dGotoLast(script, n) 108 | if !r { 109 | return false, nil 110 | } 111 | 112 | // Get the public key or multisig script hash 113 | scriptSig := "" 114 | multisig := false 115 | mm := int64(0) 116 | nn := int64(0) 117 | kPub := "" 118 | lenD := int64(0) 119 | mm, n, r = _nGet(script, n) 120 | if r { 121 | if (mm > 0 && mm < 16) { 122 | multisig = true 123 | } else { 124 | return false, nil 125 | } 126 | } 127 | if !multisig { 128 | lenD, n, r = _lGet(script, n) 129 | if !r { 130 | return false, nil 131 | } 132 | fSig := "" 133 | if lenScript > (n + int(lenD) + 2) { 134 | fSig = script[n+int(lenD):n+int(lenD)+2] 135 | } 136 | if (lenD == 64 && fSig == "ac") { 137 | kPub = script[n:n+64] 138 | n += 66 139 | scriptSig = "20" + kPub + fSig 140 | } else if (lenD == 66 && fSig == "ab") { 141 | kPub = script[n:n+66] 142 | n += 68 143 | scriptSig = "21" + kPub + fSig 144 | } else { 145 | return false, nil 146 | } 147 | } else { 148 | var kPubList []string 149 | for j := 0; j < 16; j ++ { 150 | lenD, n, r = _lGet(script, n) 151 | if !r { 152 | nn, n, r = _nGet(script, n) 153 | if (!r || len(kPubList) != int(nn)) { 154 | return false, nil 155 | } 156 | kPub, scriptSig = misc.ConvKPubListToScriptHashMultisig(mm, kPubList, nn) 157 | break 158 | } 159 | if (lenD == 64 || lenD == 66) { 160 | kPubList = append(kPubList, script[n:n+int(lenD)]) 161 | n += int(lenD) 162 | } else { 163 | return false, nil 164 | } 165 | } 166 | if lenScript < (n + 2) { 167 | return false, nil 168 | } 169 | flag = script[n:n+2] 170 | n += 2 171 | if (flag != "a9" && flag != "ae") { 172 | return false, nil 173 | } 174 | } 175 | if kPub == "" { 176 | return false, nil 177 | } 178 | // Check the protocol header. 179 | if lenScript < (n + 22) { 180 | return false, nil 181 | } 182 | flag = script[n:n+6] 183 | n += 6 184 | if flag != "006307" { 185 | return false, nil 186 | } 187 | flag = script[n:n+14] 188 | n += 14 189 | decoded, _ := hex.DecodeString(flag) 190 | header := strings.ToUpper(string(decoded[:])) 191 | if header != "KASPLEX" { 192 | return false, nil 193 | } 194 | 195 | // Get the next param data and position. 196 | _pGet := func(s string, i int) (string, int, bool) { 197 | iRaw := i 198 | lenS := len(s) 199 | lenP := int64(0) 200 | lenP, i, r = _lGet(s, i) 201 | if (!r || lenS < (i + int(lenP))) { 202 | return "", iRaw, false 203 | } 204 | if lenP == 0 { 205 | return "", i, true 206 | } 207 | decoded, _ = hex.DecodeString(s[i:i+int(lenP)]) 208 | p := string(decoded[:]) 209 | i += int(lenP) 210 | return p, i, true 211 | } 212 | 213 | // Get the param and json data. 214 | p0 := "" 215 | p1 := "" 216 | p2 := "" 217 | r = true 218 | for j := 0; j < 2; j ++ { 219 | if lenScript < (n + 2) { 220 | return false, nil 221 | } 222 | flag = script[n:n+2] 223 | n += 2 224 | if flag == "00" { 225 | p0, n, r = _pGet(script, n) 226 | } else if flag == "68" { 227 | break 228 | } else { 229 | if flag == "51" { 230 | p1 = "p1" 231 | } else if flag == "53" { 232 | p1 = "p3" 233 | } else if flag == "55" { 234 | p1 = "p5" 235 | } else if flag == "57" { 236 | p1 = "p7" 237 | } else if flag == "59" { 238 | p1 = "p9" 239 | } else if flag == "5b" { 240 | p1 = "p11" 241 | } else if flag == "5d" { 242 | p1 = "p13" 243 | } else if flag == "5f" { 244 | p1 = "p15" 245 | } else { 246 | return false, nil 247 | } 248 | p2, n, r = _pGet(script, n) 249 | } 250 | if !r { 251 | return false, nil 252 | } 253 | } 254 | if p0 == "" { 255 | return false, nil 256 | } 257 | 258 | // Get the from address. 259 | from := "" 260 | if multisig { 261 | from = misc.ConvKPubToP2sh(kPub, eRuntime.testnet) 262 | } else { 263 | from = misc.ConvKPubToAddr(kPub, eRuntime.testnet) 264 | } 265 | return true, []string{from, p0, p1, p2, scriptSig} 266 | } 267 | 268 | //////////////////////////////// 269 | // Parse the OP data in transaction. 270 | func parseOpData(txData *storage.DataTransactionType) (*storage.DataOperationType, error) { 271 | if (txData == nil || txData.Data == nil) { 272 | return nil, nil 273 | } 274 | lenInput := len(txData.Data.Inputs) 275 | if lenInput <= 0 { 276 | return nil, nil 277 | } 278 | var opScript []*storage.DataScriptType 279 | scriptSig := "" 280 | for i, input := range txData.Data.Inputs { 281 | script := input.SignatureScript 282 | isOp, scriptInfo := parseScriptInput(script) 283 | if (!isOp || scriptInfo[0] == "") { 284 | continue 285 | } 286 | decoded := storage.DataScriptType{} 287 | err := json.Unmarshal([]byte(scriptInfo[1]), &decoded) 288 | if err != nil { 289 | continue 290 | } 291 | decoded.From = scriptInfo[0] 292 | if (!eRuntime.testnet && txData.DaaScore <= 83525600 && len(txData.Data.Outputs) > 0) { // use output[0] 293 | decoded.To = txData.Data.Outputs[0].VerboseData.ScriptPublicKeyAddress 294 | } 295 | if (!ValidateP(&decoded.P) || !ValidateOp(&decoded.Op) || !ValidateAscii(&decoded.To)) { 296 | continue 297 | } 298 | operation.Method_Registered[decoded.Op].ScriptCollectEx(i, &decoded, txData, eRuntime.testnet) 299 | if !operation.Method_Registered[decoded.Op].Validate(&decoded, txData.TxId, txData.DaaScore, eRuntime.testnet) { 300 | continue 301 | } 302 | if i == 0 { 303 | opScript = append(opScript, &decoded) 304 | scriptSig = scriptInfo[4] 305 | continue 306 | } 307 | if !operation.OpRecycle_Registered[decoded.Op] { 308 | continue 309 | } 310 | opScript = append(opScript, &decoded) 311 | } 312 | if len(opScript) <= 0 { 313 | return nil, nil 314 | } 315 | opData := &storage.DataOperationType{ 316 | TxId: txData.TxId, 317 | DaaScore: txData.DaaScore, 318 | BlockAccept: txData.BlockAccept, 319 | MtsAdd: int64(txData.Data.VerboseData.BlockTime), 320 | OpScript: opScript, 321 | ScriptSig: scriptSig, 322 | SsInfo: &storage.DataStatsType{}, 323 | } 324 | return opData, nil 325 | } 326 | 327 | //////////////////////////////// 328 | // Parse the OP data in transaction list. 329 | func ParseOpDataList(txDataList []storage.DataTransactionType) ([]storage.DataOperationType, int64, error) { 330 | mtss := time.Now().UnixMilli() 331 | opDataMap := map[string]*storage.DataOperationType{} 332 | txIdMap := map[string]bool{} 333 | mutex := new(sync.RWMutex) 334 | misc.GoBatch(len(txDataList), func(i int) (error) { 335 | opData, err := parseOpData(&txDataList[i]) 336 | if err != nil { 337 | return err 338 | } 339 | if opData == nil { 340 | return nil 341 | } 342 | mutex.Lock() 343 | opDataMap[opData.TxId] = opData 344 | opDataMap[opData.TxId].FeeLeast = operation.Method_Registered[opData.OpScript[0].Op].FeeLeast(opData.DaaScore) 345 | if opDataMap[opData.TxId].FeeLeast > 0 { 346 | for _, input := range txDataList[i].Data.Inputs { 347 | txIdMap[input.PreviousOutpoint.TransactionId] = true 348 | } 349 | } 350 | mutex.Unlock() 351 | return nil 352 | }) 353 | txDataListInput := make([]storage.DataTransactionType, 0, len(txIdMap)) 354 | for txId := range txIdMap { 355 | txDataListInput = append(txDataListInput, storage.DataTransactionType{TxId: txId}) 356 | } 357 | txDataMapInput, _, err := storage.GetNodeTransactionDataMap(txDataListInput) 358 | if err != nil { 359 | return nil, 0, err 360 | } 361 | opDataList := []storage.DataOperationType{} 362 | daaScoreNow := uint64(0) 363 | opScore := uint64(0) 364 | for _, txData := range txDataList { 365 | if opDataMap[txData.TxId] == nil { 366 | continue 367 | } 368 | if daaScoreNow != txData.DaaScore { 369 | daaScoreNow = txData.DaaScore 370 | opScore = daaScoreNow * 10000 371 | } 372 | opDataMap[txData.TxId].OpScore = opScore 373 | if opDataMap[txData.TxId].FeeLeast > 0 { 374 | amountIn := uint64(0) 375 | amountOut := uint64(0) 376 | for _, output := range txData.Data.Outputs { 377 | amountOut += output.Amount 378 | } 379 | for _, input := range txData.Data.Inputs { 380 | if txDataMapInput[input.PreviousOutpoint.TransactionId] == nil { 381 | continue 382 | } 383 | amountIn += txDataMapInput[input.PreviousOutpoint.TransactionId].Outputs[input.PreviousOutpoint.Index].Amount 384 | } 385 | if amountIn <= amountOut { 386 | opDataMap[txData.TxId].Fee = 0 387 | continue 388 | } 389 | opDataMap[txData.TxId].Fee = amountIn - amountOut 390 | } 391 | opDataList = append(opDataList, *opDataMap[txData.TxId]) 392 | opScore ++ 393 | } 394 | return opDataList, time.Now().UnixMilli() - mtss, nil 395 | } 396 | 397 | //////////////////////////////// 398 | func ValidateP(p *string) (bool) { 399 | *p = strings.ToUpper(*p) 400 | if !operation.P_Registered[*p] { 401 | return false 402 | } 403 | return true 404 | } 405 | 406 | //////////////////////////////// 407 | func ValidateOp(op *string) (bool) { 408 | *op = strings.ToLower(*op) 409 | if !operation.Op_Registered[*op] { 410 | return false 411 | } 412 | return true 413 | } 414 | 415 | //////////////////////////////// 416 | func ValidateAscii(s *string) (bool) { 417 | if *s == "" { 418 | return true 419 | } 420 | for _, c := range *s { 421 | if c > unicode.MaxASCII { 422 | return false 423 | } 424 | } 425 | return true 426 | } 427 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module kasplex-executor 2 | 3 | go 1.22.5 4 | 5 | require ( 6 | github.com/gocql/gocql v1.6.0 7 | github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c 8 | golang.org/x/crypto v0.26.0 9 | google.golang.org/grpc v1.65.0 10 | google.golang.org/protobuf v1.34.2 11 | ) 12 | 13 | require ( 14 | github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect 15 | github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect 16 | github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect 17 | github.com/golang/snappy v0.0.4 // indirect 18 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 19 | golang.org/x/net v0.28.0 // indirect 20 | golang.org/x/sys v0.24.0 // indirect 21 | golang.org/x/text v0.17.0 // indirect 22 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect 23 | gopkg.in/inf.v0 v0.9.1 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= 2 | github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= 3 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 4 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 5 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= 8 | github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= 9 | github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= 10 | github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= 11 | github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= 12 | github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= 13 | github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU= 14 | github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= 15 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 16 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 17 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 18 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 19 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 20 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= 21 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= 22 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 23 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 24 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 25 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 26 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 29 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 30 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 31 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 32 | github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= 33 | github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= 34 | golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= 35 | golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= 36 | golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= 37 | golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 38 | golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= 39 | golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 40 | golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= 41 | golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 42 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= 43 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 44 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 45 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 46 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 47 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 48 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 49 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 50 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package main 4 | 5 | import ( 6 | "os" 7 | "fmt" 8 | "log" 9 | "sync" 10 | "strings" 11 | "syscall" 12 | "context" 13 | "log/slog" 14 | "os/signal" 15 | "path/filepath" 16 | "kasplex-executor/config" 17 | "kasplex-executor/storage" 18 | "kasplex-executor/explorer" 19 | ) 20 | 21 | //////////////////////////////// 22 | func main() { 23 | fmt.Println("KASPlex Executor v"+config.Version) 24 | 25 | // Set the correct working directory. 26 | arg0 := os.Args[0] 27 | if strings.Index(arg0, "go-build") < 0 { 28 | dir, err := filepath.Abs(filepath.Dir(arg0)) 29 | if err != nil { 30 | log.Fatalln("main fatal:", err.Error()) 31 | } 32 | os.Chdir(dir) 33 | } 34 | 35 | // Use the file lock for startup. 36 | fLock := "./.lockExecutor" 37 | lock, err := os.Create(fLock) 38 | if err != nil { 39 | log.Fatalln("main fatal:", err.Error()) 40 | } 41 | defer os.Remove(fLock) 42 | defer lock.Close() 43 | err = syscall.Flock(int(lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 44 | if err != nil { 45 | log.Fatalln("main fatal:", err.Error()) 46 | } 47 | defer syscall.Flock(int(lock.Fd()), syscall.LOCK_UN) 48 | 49 | // Load config. 50 | var cfg config.Config 51 | config.Load(&cfg) 52 | 53 | // Set the log level. 54 | logOpt := &slog.HandlerOptions{Level: slog.LevelError,} 55 | if cfg.Debug == 3 { 56 | logOpt = &slog.HandlerOptions{Level: slog.LevelDebug,} 57 | } else if cfg.Debug == 2 { 58 | logOpt = &slog.HandlerOptions{Level: slog.LevelInfo,} 59 | } else if cfg.Debug == 1 { 60 | logOpt = &slog.HandlerOptions{Level: slog.LevelWarn,} 61 | } 62 | logHandler := slog.NewTextHandler(os.Stdout, logOpt) 63 | slog.SetDefault(slog.New(logHandler)) 64 | 65 | // Set exit signal. 66 | ctx, cancel := context.WithCancel(context.Background()) 67 | wg := &sync.WaitGroup{} 68 | wg.Add(1) 69 | c := make(chan os.Signal) 70 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 71 | down := false 72 | go func() { 73 | <-c 74 | slog.Info("main stopping ..") 75 | cancel() 76 | down = true 77 | wg.Done() 78 | }() 79 | 80 | // Init storage driver. 81 | storage.Init(cfg.Cassandra, cfg.Rocksdb) 82 | 83 | // Init explorer if api server up. 84 | if (!down) { 85 | explorer.Init(ctx, wg, cfg.Startup, cfg.Testnet) 86 | go explorer.Run() 87 | } 88 | 89 | // Waiting 90 | wg.Wait() 91 | } 92 | -------------------------------------------------------------------------------- /misc/address.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package misc 4 | 5 | import ( 6 | "fmt" 7 | "strconv" 8 | "encoding/hex" 9 | "golang.org/x/crypto/blake2b" 10 | ) 11 | 12 | //////////////////////////////// 13 | // Convert the public key list to script hash of multisig. 14 | func ConvKPubListToScriptHashMultisig(m int64, kPubList []string, n int64) (string, string) { 15 | lenKPubList := len(kPubList) 16 | if (lenKPubList < 1 || lenKPubList != int(n)) { 17 | return "", "" 18 | } 19 | ecdsa := false 20 | kPub := strconv.FormatInt(m+80, 16) 21 | for _, k := range kPubList { 22 | lenK := len(k) 23 | if lenK == 64 { 24 | kPub += "20" 25 | } else if lenK == 66 { 26 | kPub += "21" 27 | ecdsa = true 28 | } else { 29 | return "", "" 30 | } 31 | kPub += k 32 | } 33 | kPub += strconv.FormatInt(n+80, 16) 34 | if ecdsa { 35 | kPub += "a9" 36 | } else { 37 | kPub += "ae" 38 | } 39 | decoded, _ := hex.DecodeString(kPub) 40 | sum := blake2b.Sum256(decoded) 41 | return fmt.Sprintf("%064x", string(sum[:])), kPub 42 | } 43 | 44 | //////////////////////////////// 45 | // Convert the script hash to address. 46 | func ConvKPubToP2sh(kPub string, testnet bool) (string) { 47 | lenKey := len(kPub) 48 | if lenKey != 64 { 49 | return "" 50 | } 51 | kPub = "08" + kPub // P2SH ver 52 | decoded, _ := hex.DecodeString(kPub) 53 | kPub = string(decoded[:]) 54 | addr := EncodeBech32(kPub, testnet) 55 | if testnet { 56 | addr = "kaspatest:" + addr 57 | } else { 58 | addr = "kaspa:" + addr 59 | } 60 | return addr 61 | } 62 | 63 | //////////////////////////////// 64 | // Convert the public key to address. 65 | func ConvKPubToAddr(kPub string, testnet bool) (string) { 66 | lenKey := len(kPub) 67 | if lenKey == 64 { // Schnorr ver 68 | kPub = "00" + kPub 69 | } else if lenKey == 66 { // Ecdsa ver 70 | kPub = "01" + kPub 71 | } else { 72 | return "" 73 | } 74 | decoded, _ := hex.DecodeString(kPub) 75 | kPub = string(decoded[:]) 76 | addr := EncodeBech32(kPub, testnet) 77 | if testnet { 78 | addr = "kaspatest:" + addr 79 | } else { 80 | addr = "kaspa:" + addr 81 | } 82 | return addr 83 | } 84 | 85 | //////////////////////////////// 86 | // Convert the address to public key or script hash. 87 | func ConvAddrToKPub(addr string, testnet bool) (string, string) { 88 | s := 6 89 | if testnet { 90 | s = 10 91 | } 92 | if (!testnet && (len(addr) < 67 || addr[0:s] != "kaspa:")) { 93 | return "", "" 94 | } 95 | if (testnet && (len(addr) < 71 || addr[0:s] != "kaspatest:")) { 96 | return "", "" 97 | } 98 | kPub := hex.EncodeToString([]byte(DecodeBech32(addr[s:], testnet))) 99 | if len(kPub) < 64 { 100 | return "", "" 101 | } 102 | return kPub[:2], kPub[2:] 103 | } 104 | 105 | //////////////////////////////// 106 | // Verify the address. 107 | func VerifyAddr(addr string, testnet bool) (bool) { 108 | ver, kPub := ConvAddrToKPub(addr, testnet) 109 | if kPub == "" { 110 | return false 111 | } 112 | addr2 := "" 113 | if ver == "08" { 114 | addr2 = ConvKPubToP2sh(kPub, testnet) 115 | } else { 116 | addr2 = ConvKPubToAddr(kPub, testnet) 117 | } 118 | if addr2 != addr { 119 | return false 120 | } 121 | return true 122 | } 123 | 124 | //////////////////////////////// 125 | // Make the hex data of script. 126 | func MakeScriptHex(data string) (string) { 127 | hexData := "" 128 | lenData := len(data) 129 | if lenData <= 0 { 130 | return "00" 131 | } else if lenData <= 75 { 132 | hexData = fmt.Sprintf("%02x", lenData) 133 | } else if lenData <= 255 { 134 | hexData = "4c" + fmt.Sprintf("%02x", lenData) 135 | } else { 136 | hexData = "4d" + fmt.Sprintf("%04x", lenData) 137 | } 138 | hexData += hex.EncodeToString([]byte(data)) 139 | return hexData 140 | } 141 | 142 | //////////////////////////////// 143 | // Make the script with the protocol. 144 | func MakeP2shKasplex(scriptSig string, scriptPn string, strJson string, testnet bool) (string, string) { 145 | scriptJson := "00" + MakeScriptHex(strJson) 146 | script := scriptSig 147 | script += "0063076b6173706c6578" 148 | script += scriptPn 149 | script += scriptJson 150 | script += "68" 151 | bin, _ := hex.DecodeString(script) 152 | sum := blake2b.Sum256(bin) 153 | scriptHash := fmt.Sprintf("%064x", string(sum[:])) 154 | return ConvKPubToP2sh(scriptHash, testnet), script 155 | } 156 | 157 | //////////////////////////////// 158 | func EncodeBech32(data string, testnet bool) (string) { 159 | _pMod := func(list []byte) int { 160 | g := []int{0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470} 161 | cs := 1 162 | for _, v := range list { 163 | b := cs >> 35 164 | cs = ((cs & 0x07ffffffff) << 5) ^ int(v) 165 | for i := 0; i < len(g); i++ { 166 | if ((b >> uint(i)) & 1) == 1 { 167 | cs ^= g[i] 168 | } 169 | } 170 | } 171 | return cs ^ 1 172 | } 173 | b8 := []byte(data) 174 | b8Len := len(b8) 175 | nLast := 0 176 | bLast := byte(0) 177 | var b5 []byte 178 | for i :=0; i < b8Len; i++ { 179 | rMove := 3 + nLast 180 | b := (b8[i] >> rMove) & 31 181 | if nLast > 0 { 182 | b |= bLast 183 | } 184 | b5 = append(b5, b) 185 | nLast = rMove 186 | if rMove >= 5 { 187 | b5 = append(b5, (b8[i] << (8-rMove) >> 3) & 31) 188 | nLast = rMove - 5 189 | } 190 | if nLast > 0 { 191 | bLast = (b8[i] << (8-nLast) >> 3) & 31 192 | } 193 | } 194 | if nLast > 0 { 195 | b5 = append(b5, bLast) 196 | } 197 | b5ex := []byte{11, 1, 19, 16, 1, 0} 198 | if testnet { 199 | b5ex = []byte{11, 1, 19, 16, 1, 20, 5, 19, 20, 0} 200 | } 201 | b5ex = append(b5ex, b5...) 202 | b5ex = append(b5ex, 0, 0, 0, 0, 0, 0, 0, 0) 203 | p := _pMod(b5ex) 204 | c := []string{"q", "p", "z", "r", "y", "9", "x", "8", "g", "f", "2", "t", "v", "d", "w", "0", "s", "3", "j", "n", "5", "4", "k", "h", "c", "e", "6", "m", "u", "a", "7", "l"} 205 | for i := 0; i < 8; i++ { 206 | b5 = append(b5, byte((p >> (5*(7-i))) & 31)) 207 | } 208 | result := "" 209 | for i := 0; i < len(b5); i++ { 210 | result += c[int(b5[i])] 211 | } 212 | return result 213 | } 214 | 215 | //////////////////////////////// 216 | func DecodeBech32(data string, testnet bool) (string) { 217 | _pMod := func(list []byte) int { 218 | g := []int{0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470} 219 | cs := 1 220 | for _, v := range list { 221 | b := cs >> 35 222 | cs = ((cs & 0x07ffffffff) << 5) ^ int(v) 223 | for i := 0; i < len(g); i++ { 224 | if ((b >> uint(i)) & 1) == 1 { 225 | cs ^= g[i] 226 | } 227 | } 228 | } 229 | return cs ^ 1 230 | } 231 | n := map[string]byte{"q":0, "p":1, "z":2, "r":3, "y":4, "9":5, "x":6, "8":7, "g":8, "f":9, "2":10, "t":11, "v":12, "d":13, "w":14, "0":15, "s":16, "3":17, "j":18, "n":19, "5":20, "4":21, "k":22, "h":23, "c":24, "e":25, "6":26, "m":27, "u":28, "a":29, "7":30, "l":31} 232 | dataLen := len(data) 233 | var b5 []byte 234 | for i :=0; i < dataLen; i++ { 235 | _, existed := n[data[i:i+1]] 236 | if !existed { 237 | return "" 238 | } 239 | b5 = append(b5, n[data[i:i+1]]) 240 | } 241 | b5Len := len(b5) 242 | cs := b5[b5Len-8:] 243 | b5 = b5[:b5Len-8] 244 | b5Len -= 8 245 | b5ex := []byte{11, 1, 19, 16, 1, 0} 246 | if testnet { 247 | b5ex = []byte{11, 1, 19, 16, 1, 20, 5, 19, 20, 0} 248 | } 249 | b5ex = append(b5ex, b5...) 250 | b5ex = append(b5ex, 0, 0, 0, 0, 0, 0, 0, 0) 251 | p := _pMod(b5ex) 252 | for i := 0; i < 8; i++ { 253 | if cs[i] != byte((p >> (5*(7-i))) & 31) { 254 | return "" 255 | } 256 | } 257 | var b8 []byte 258 | nLast := 0 259 | bLast := byte(0) 260 | for i :=0; i < b5Len; i++ { 261 | offset := 3 - nLast 262 | if offset == 0 { 263 | b8 = append(b8, b5[i] | bLast) 264 | nLast = 0 265 | bLast = 0 266 | } else if offset < 0 { 267 | b8 = append(b8, (b5[i] >> (-offset)) | bLast) 268 | nLast = -offset 269 | bLast = b5[i] << (8-nLast) 270 | } else { 271 | bLast |= b5[i] << offset 272 | nLast += 5 273 | } 274 | } 275 | return string(b8) 276 | } 277 | -------------------------------------------------------------------------------- /misc/batch.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package misc 4 | 5 | import ( 6 | "time" 7 | "sync" 8 | "math" 9 | ) 10 | 11 | //////////////////////////////// 12 | const nGoroutine = 100 13 | 14 | //////////////////////////////// 15 | func GoBatch(lenBatch int, fGo func(int) (error)) (int64, error) { 16 | if lenBatch <= 0 { 17 | return 0, nil 18 | } 19 | mtss := time.Now().UnixMilli() 20 | nBatch := int(math.Ceil(float64(lenBatch) / float64(nGoroutine))) 21 | wg := &sync.WaitGroup{} 22 | errList := make(chan error, nBatch) 23 | for i := 0; i < nGoroutine; i ++ { 24 | wg.Add(1) 25 | go func() { 26 | for j := i*nBatch; j < (i+1)*nBatch; j ++ { 27 | if j >= lenBatch { 28 | break 29 | } 30 | err := fGo(j) 31 | if err != nil { 32 | errList <- err 33 | break 34 | } 35 | } 36 | wg.Done() 37 | }() 38 | } 39 | wg.Wait() 40 | if len(errList) > 0 { 41 | err := <- errList 42 | return 0, err 43 | } 44 | return time.Now().UnixMilli() - mtss, nil 45 | } 46 | -------------------------------------------------------------------------------- /operation/init.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | "strconv" 9 | "strings" 10 | //"log/slog" 11 | "math/big" 12 | "encoding/hex" 13 | "golang.org/x/crypto/blake2b" 14 | "kasplex-executor/storage" 15 | ) 16 | 17 | //////////////////////////////// 18 | type OpMethod interface { 19 | ScriptCollectEx(int, *storage.DataScriptType, *storage.DataTransactionType, bool) 20 | Validate(*storage.DataScriptType, string, uint64, bool) (bool) 21 | FeeLeast(uint64) (uint64) 22 | PrepareStateKey(*storage.DataScriptType, storage.DataStateMapType) 23 | Do(int, *storage.DataOperationType, storage.DataStateMapType, bool) (error) 24 | //UnDo() (error) 25 | // ... 26 | } 27 | 28 | //////////////////////////////// 29 | var P_Registered = map[string]bool{} 30 | var Op_Registered = map[string]bool{} 31 | var Method_Registered = map[string]OpMethod{} 32 | var OpRecycle_Registered = map[string]bool{} 33 | 34 | //////////////////////////////// 35 | var TickIgnored = map[string]bool{ 36 | "KASPA": true, "KASPLX": true, 37 | "KASP": true, "WKAS": true, 38 | "GIGA": true, "WBTC": true, 39 | "WETH": true, "USDT": true, 40 | "USDC": true, "FDUSD": true, 41 | "USDD": true, "TUSD": true, 42 | "USDP": true, "PYUSD": true, 43 | "EURC": true, "BUSD": true, 44 | "GUSD": true, "EURT": true, 45 | "XAUT": true, "TETHER": true, 46 | // ... 47 | } 48 | 49 | //////////////////////////////// 50 | var TickReserved = map[string]string{ 51 | "NACHO": "kaspa:qzrsq2mfj9sf7uye3u5q7juejzlr0axk5jz9fpg4vqe76erdyvxxze84k9nk7", 52 | "KCATS": "kaspa:qq8guq855gxkfrj2w25skwgj7cp4hy08x6a8mz70tdtmgv5p2ngwqxpj4cknc", 53 | "KASTOR": "kaspa:qr8vt54764aaddejhjfwtsh07jcjr49v38vrw2vtmxxtle7j2uepynwy57ufg", 54 | "KASPER": "kaspa:qppklkx2zyr2g2djg3uy2y2tsufwsqjk36pt27vt2xfu8uqm24pskk4p7tq5n", 55 | "FUSUN": "kaspa:qzp30gu5uty8jahu9lq5vtplw2ca8m2k7p45ez3y8jf9yrm5qdxquq5nl45t5", 56 | "KPAW": "kaspa:qpp0y685frmnlvhmnz5t6qljatumqm9zmppwnhwu9vyyl6w8nt30qjedekmdw", 57 | "PPKAS": "kaspa:qrlx9377yje3gvj9qxvwnn697d209lshgcrvge3yzlxnvyrfyk3q583jh3cmz", 58 | "GHOAD": "kaspa:qpkty3ymqs67t0z3g7l457l79f9k6drl55uf2qeq5tlkrpf3zwh85es0xtaj9", 59 | "KEPE": "kaspa:qq45gur2grn80uuegg9qgewl0wg2ahz5n4qm9246laej9533f8e22x3xe6hkm", 60 | "WORI": "kaspa:qzhgepc7mjscszkteeqhy99d3v96ftpg2wyy6r85nd0kg9m8rfmusqpp7mxkq", 61 | "KEKE": "kaspa:qqq9m42mdcvlz8c7r9kmpqj59wkfx3nppqte8ay20m4p46x3z0lsyzz34h8uf", 62 | "DOGK": "kaspa:qpsj64nxtlwceq4e7jvrsrkl0y6dayfyrqr49pep7pd2tq2uzvk7ks7n0qwxc", 63 | "BTAI": "kaspa:qp0na29g4lysnaep5pmg9xkdzcn4xm4a35ha5naq79ns9mcgc3pccnf225qma", 64 | "KASBOT": "kaspa:qrrcpdaev9augqwy8jnnp20skplyswa7ezz3m9ex3ryxw22frpzpj2xx99scq", 65 | "SOMPS": "kaspa:qry7xqy6s7d449gqyl0dkr99x6df0q5jlj6u52p84tfv6rddxjrucnn066237", 66 | "KREP": "kaspa:qzaclsmr5vttzlt0rz0x3shnudny8lnz5zpmjr4lp9v7aa7u7zvexh05eqwq0", 67 | // ... 68 | } 69 | 70 | //////////////////////////////// 71 | func ApplyTickReserved(reservedList []string) { 72 | for _, reserved := range reservedList { 73 | tickAddr := strings.Split(reserved, "_") 74 | if len(tickAddr) < 2 { 75 | continue 76 | } 77 | TickReserved[tickAddr[0]] = tickAddr[1] 78 | } 79 | } 80 | 81 | //////////////////////////////// 82 | func PrepareStateBatch(opDataList []storage.DataOperationType) (storage.DataStateMapType, int64, error) { 83 | mtss := time.Now().UnixMilli() 84 | stateMap := storage.DataStateMapType{ 85 | StateTokenMap: make(map[string]*storage.StateTokenType), 86 | StateBalanceMap: make(map[string]*storage.StateBalanceType), 87 | StateMarketMap: make(map[string]*storage.StateMarketType), 88 | StateBlacklistMap: make(map[string]*storage.StateBlacklistType), 89 | // StateXxx ... 90 | } 91 | for _, opData := range opDataList{ 92 | for _, opScript := range opData.OpScript{ 93 | Method_Registered[opScript.Op].PrepareStateKey(opScript, stateMap) 94 | } 95 | } 96 | _, err := storage.GetStateTokenMap(stateMap.StateTokenMap) 97 | if err != nil { 98 | return storage.DataStateMapType{}, 0, err 99 | } 100 | _, err = storage.GetStateBalanceMap(stateMap.StateBalanceMap) 101 | if err != nil { 102 | return storage.DataStateMapType{}, 0, err 103 | } 104 | _, err = storage.GetStateMarketMap(stateMap.StateMarketMap) 105 | if err != nil { 106 | return storage.DataStateMapType{}, 0, err 107 | } 108 | _, err = storage.GetStateBlacklistMap(stateMap.StateBlacklistMap) 109 | if err != nil { 110 | return storage.DataStateMapType{}, 0, err 111 | } 112 | // GetStateXxx ... 113 | return stateMap, time.Now().UnixMilli() - mtss, nil 114 | } 115 | 116 | //////////////////////////////// 117 | func ExecuteBatch(opDataList []storage.DataOperationType, stateMap storage.DataStateMapType, checkpointLast string, testnet bool) (storage.DataRollbackType, int64, error) { 118 | mtss := time.Now().UnixMilli() 119 | rollback := storage.DataRollbackType{ 120 | CheckpointBefore: checkpointLast, 121 | OpScoreList: []uint64{}, 122 | TxIdList: []string{}, 123 | } 124 | if len(opDataList) <= 0 { 125 | return rollback, 0, nil 126 | } 127 | storage.CopyDataStateMap(stateMap, &rollback.StateMapBefore) 128 | for i := range opDataList { 129 | opData := &opDataList[i] 130 | if (testnet && opData.DaaScore%100000 <= 9) { 131 | checkpointLast = "" 132 | } 133 | iScriptAccept := -1 134 | opError := "" 135 | for iScript, opScript := range opData.OpScript{ 136 | opData.OpAccept = 0 137 | opData.OpError = "" 138 | err := Method_Registered[opScript.Op].Do(iScript, opData, stateMap, testnet) 139 | if err != nil { 140 | return storage.DataRollbackType{}, 0, err 141 | } 142 | if (opData.OpAccept == 1 && iScriptAccept < 0) { 143 | iScriptAccept = iScript 144 | } 145 | if (opData.OpAccept == -1 && opError == "") { 146 | opError = opData.OpError 147 | } 148 | } 149 | if iScriptAccept >= 0 { 150 | opData.OpAccept = 1 151 | opData.OpError = "" 152 | if iScriptAccept > 0 { 153 | opData.OpScript = opData.OpScript[iScriptAccept:] 154 | } 155 | } else { 156 | opData.OpAccept = -1 157 | opData.OpError = opError 158 | } 159 | if opData.OpAccept == 1 { 160 | cpHeader := strconv.FormatUint(opData.OpScore,10) +","+ opData.TxId +","+ opData.BlockAccept +","+ opData.OpScript[0].P +","+ opData.OpScript[0].Op 161 | sum := blake2b.Sum256([]byte(cpHeader)) 162 | cpHeader = fmt.Sprintf("%064x", string(sum[:])) 163 | cpState := strings.Join(opData.StAfter, ";") 164 | sum = blake2b.Sum256([]byte(cpState)) 165 | cpState = fmt.Sprintf("%064x", string(sum[:])) 166 | sum = blake2b.Sum256([]byte(checkpointLast + cpHeader + cpState)) 167 | opData.Checkpoint = fmt.Sprintf("%064x", string(sum[:])) 168 | checkpointLast = opData.Checkpoint 169 | } 170 | rollback.OpScoreLast = opData.OpScore 171 | rollback.OpScoreList = append(rollback.OpScoreList, opData.OpScore) 172 | rollback.TxIdList = append(rollback.TxIdList, opData.TxId) 173 | } 174 | rollback.CheckpointAfter = checkpointLast 175 | return rollback, time.Now().UnixMilli() - mtss, nil 176 | } 177 | 178 | //////////////////////////////// 179 | func MakeStLineToken(key string, stToken *storage.StateTokenType, isDeploy bool) (string) { 180 | stLine := storage.KeyPrefixStateToken + key 181 | if stToken == nil { 182 | return stLine 183 | } 184 | stLine += "," 185 | strDec := strconv.Itoa(stToken.Dec) 186 | opScore := stToken.OpMod 187 | /*if isDeploy { 188 | opScore = stToken.OpAdd 189 | }*/ 190 | strOpscore := strconv.FormatUint(opScore, 10) 191 | if isDeploy { 192 | stLine += stToken.Max + "," 193 | stLine += stToken.Lim + "," 194 | stLine += stToken.Pre + "," 195 | stLine += strDec + "," 196 | stLine += stToken.From + "," 197 | stLine += stToken.To + "," 198 | } 199 | stLine += stToken.Minted + "," 200 | stLine += strOpscore 201 | if stToken.Mod == "issue" { 202 | stLine += "," + stToken.Mod 203 | stLine += "," + stToken.Burned 204 | stLine += "," + stToken.Name 205 | } 206 | return stLine 207 | } 208 | func AppendStLineToken(stLine []string, key string, stToken *storage.StateTokenType, isDeploy bool, isAfter bool) ([]string) { 209 | keyFull := storage.KeyPrefixStateToken + key 210 | iExists := -1 211 | list := []string{} 212 | for i, line := range stLine { 213 | list = strings.SplitN(line, ",", 2) 214 | if list[0] == keyFull { 215 | iExists = i 216 | break 217 | } 218 | } 219 | if iExists < 0 { 220 | return append(stLine, MakeStLineToken(key, stToken, isDeploy)) 221 | } 222 | if isAfter { 223 | stLine[iExists] = MakeStLineToken(key, stToken, isDeploy) 224 | } 225 | return stLine 226 | } 227 | 228 | //////////////////////////////// 229 | func MakeStLineBalance(key string, stBalance *storage.StateBalanceType) (string) { 230 | stLine := storage.KeyPrefixStateBalance + key 231 | if stBalance == nil { 232 | return stLine 233 | } 234 | stLine += "," 235 | strDec := strconv.Itoa(stBalance.Dec) 236 | strOpscore := strconv.FormatUint(stBalance.OpMod, 10) 237 | stLine += strDec + "," 238 | stLine += stBalance.Balance + "," 239 | stLine += stBalance.Locked + "," 240 | stLine += strOpscore 241 | return stLine 242 | } 243 | func AppendStLineBalance(stLine []string, key string, stBalance *storage.StateBalanceType, isAfter bool) ([]string) { 244 | keyFull := storage.KeyPrefixStateBalance + key 245 | iExists := -1 246 | list := []string{} 247 | for i, line := range stLine { 248 | list = strings.SplitN(line, ",", 2) 249 | if list[0] == keyFull { 250 | iExists = i 251 | break 252 | } 253 | } 254 | if iExists < 0 { 255 | return append(stLine, MakeStLineBalance(key, stBalance)) 256 | } 257 | if isAfter { 258 | stLine[iExists] = MakeStLineBalance(key, stBalance) 259 | } 260 | return stLine 261 | } 262 | 263 | //////////////////////////////// 264 | func MakeStLineMarket(key string, stMarket *storage.StateMarketType) (string) { 265 | stLine := storage.KeyPrefixStateMarket + key 266 | if stMarket == nil { 267 | return stLine 268 | } 269 | stLine += "," 270 | strOpscore := strconv.FormatUint(stMarket.OpAdd, 10) 271 | stLine += stMarket.UAddr + "," 272 | stLine += stMarket.UAmt + "," 273 | stLine += stMarket.TAmt + "," 274 | stLine += strOpscore 275 | return stLine 276 | } 277 | func AppendStLineMarket(stLine []string, key string, stMarket *storage.StateMarketType, isAfter bool) ([]string) { 278 | keyFull := storage.KeyPrefixStateMarket + key 279 | iExists := -1 280 | list := []string{} 281 | for i, line := range stLine { 282 | list = strings.SplitN(line, ",", 2) 283 | if list[0] == keyFull { 284 | iExists = i 285 | break 286 | } 287 | } 288 | if iExists < 0 { 289 | return append(stLine, MakeStLineMarket(key, stMarket)) 290 | } 291 | if isAfter { 292 | stLine[iExists] = MakeStLineMarket(key, stMarket) 293 | } 294 | return stLine 295 | } 296 | 297 | //////////////////////////////// 298 | func MakeStLineBlacklist(key string, stBlacklist *storage.StateBlacklistType) (string) { 299 | stLine := storage.KeyPrefixStateBlacklist + key 300 | if stBlacklist == nil { 301 | return stLine 302 | } 303 | stLine += "," 304 | strOpscore := strconv.FormatUint(stBlacklist.OpAdd, 10) 305 | stLine += strOpscore 306 | return stLine 307 | } 308 | func AppendStLineBlacklist(stLine []string, key string, stBlacklist *storage.StateBlacklistType, isAfter bool) ([]string) { 309 | keyFull := storage.KeyPrefixStateBlacklist + key 310 | iExists := -1 311 | list := []string{} 312 | for i, line := range stLine { 313 | list = strings.SplitN(line, ",", 2) 314 | if list[0] == keyFull { 315 | iExists = i 316 | break 317 | } 318 | } 319 | if iExists < 0 { 320 | return append(stLine, MakeStLineBlacklist(key, stBlacklist)) 321 | } 322 | if isAfter { 323 | stLine[iExists] = MakeStLineBlacklist(key, stBlacklist) 324 | } 325 | return stLine 326 | } 327 | 328 | //////////////////////////////// 329 | func AppendSsInfoTickAffc(tickAffc []string, key string, value int64) ([]string) { 330 | iExists := -1 331 | valueBefore := int64(0) 332 | list := []string{} 333 | for i, affc := range tickAffc { 334 | list = strings.SplitN(affc, "=", 2) 335 | if list[0] == key { 336 | iExists = i 337 | if len(list) > 1 { 338 | valueBefore, _ = strconv.ParseInt(list[1], 10, 64) 339 | } 340 | break 341 | } 342 | } 343 | if iExists < 0 { 344 | return append(tickAffc, key+"="+strconv.FormatInt(value, 10)) 345 | } 346 | tickAffc[iExists] = key+"="+strconv.FormatInt(value+valueBefore, 10) 347 | return tickAffc 348 | } 349 | 350 | //////////////////////////////// 351 | func AppendSsInfoAddressAffc(addressAffc []string, key string, value string) ([]string) { 352 | iExists := -1 353 | list := []string{} 354 | for i, affc := range addressAffc { 355 | list = strings.SplitN(affc, "=", 2) 356 | if list[0] == key { 357 | iExists = i 358 | break 359 | } 360 | } 361 | if iExists < 0 { 362 | return append(addressAffc, key+"="+value) 363 | } 364 | addressAffc[iExists] = key+"="+value 365 | return addressAffc 366 | } 367 | 368 | //////////////////////////////// 369 | func ValidateTick(tick *string) (bool) { 370 | *tick = strings.ToUpper(*tick) 371 | lenTick := len(*tick) 372 | if (lenTick < 4 || lenTick > 6) { 373 | return false 374 | } 375 | for i := 0; i < lenTick; i++ { 376 | if ((*tick)[i] < 65 || (*tick)[i] > 90) { 377 | return false 378 | } 379 | } 380 | return true 381 | } 382 | //////////////////////////////// 383 | func ValidateTxId(tick *string) (bool) { 384 | *tick = strings.ToLower(*tick) 385 | if len(*tick) != 64 { 386 | return false 387 | } 388 | _, err := hex.DecodeString(*tick) 389 | if err != nil { 390 | return false 391 | } 392 | return true 393 | } 394 | //////////////////////////////// 395 | func ValidateTickTxId(tick *string) (bool) { 396 | if len(*tick) < 64 { 397 | return ValidateTick(tick) 398 | } 399 | return ValidateTxId(tick) 400 | } 401 | //////////////////////////////// 402 | func ValidateAmount(amount *string) (bool) { 403 | if *amount == "" { 404 | *amount = "0" 405 | return false 406 | } 407 | amountBig := new(big.Int) 408 | _, s := amountBig.SetString(*amount, 10) 409 | if !s { 410 | return false 411 | } 412 | amount2 := amountBig.Text(10) 413 | if *amount != amount2 { 414 | return false 415 | } 416 | limitBig := new(big.Int) 417 | limitBig.SetString("0", 10) 418 | if limitBig.Cmp(amountBig) >= 0 { 419 | return false 420 | } 421 | limitBig.SetString("99999999999999999999999999999999", 10) 422 | if amountBig.Cmp(limitBig) > 0 { 423 | return false 424 | } 425 | return true 426 | } 427 | 428 | //////////////////////////////// 429 | func ValidateDec(dec *string, def string) (bool) { 430 | if *dec == "" { 431 | *dec = def 432 | return true 433 | } 434 | decInt, err := strconv.Atoi(*dec) 435 | if err != nil { 436 | return false 437 | } 438 | decString := strconv.Itoa(decInt) 439 | if (decString != *dec || decInt < 0 || decInt > 18) { 440 | return false 441 | } 442 | return true 443 | } 444 | 445 | //////////////////////////////// 446 | func ValidationUint(value *string, def string) (bool) { 447 | if *value == "" { 448 | *value = def 449 | return true 450 | } 451 | valueUint, err := strconv.ParseUint(*value, 10, 64) 452 | if err != nil { 453 | return false 454 | } 455 | valueString := strconv.FormatUint(valueUint, 10) 456 | if (valueString != *value) { 457 | return false 458 | } 459 | return true 460 | } 461 | 462 | // ... 463 | -------------------------------------------------------------------------------- /operation/op_blacklist.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "kasplex-executor/misc" 7 | "kasplex-executor/storage" 8 | ) 9 | 10 | //////////////////////////////// 11 | type OpMethodBlacklist struct {} 12 | 13 | //////////////////////////////// 14 | func init() { 15 | opName := "blacklist" 16 | P_Registered["KRC-20"] = true 17 | Op_Registered[opName] = true 18 | Method_Registered[opName] = new(OpMethodBlacklist) 19 | } 20 | 21 | //////////////////////////////// 22 | func (opMethodBlacklist OpMethodBlacklist) FeeLeast(daaScore uint64) (uint64) { 23 | // if daaScore ... 24 | return 0 25 | } 26 | 27 | //////////////////////////////// 28 | func (opMethodBlacklist OpMethodBlacklist) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) {} 29 | 30 | //////////////////////////////// 31 | func (opMethodBlacklist OpMethodBlacklist) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 32 | if (!testnet && daaScore < 110165000) { 33 | return false 34 | } 35 | if (script.From == "" || script.To == "" || script.P != "KRC-20" || !ValidateTxId(&script.Ca)) { 36 | return false 37 | } 38 | if (script.Mod != "add" && script.Mod != "remove") { 39 | return false 40 | } 41 | script.Tick = script.Ca 42 | script.Amt = "" 43 | script.Max = "" 44 | script.Lim = "" 45 | script.Pre = "" 46 | script.Dec = "" 47 | script.Utxo = "" 48 | script.Price = "" 49 | script.Name = "" 50 | script.Ca = "" 51 | return true 52 | } 53 | 54 | //////////////////////////////// 55 | func (opMethodBlacklist OpMethodBlacklist) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 56 | stateMap.StateTokenMap[opScript.Tick] = nil 57 | stateMap.StateBlacklistMap[opScript.Tick+"_"+opScript.To] = nil 58 | } 59 | 60 | //////////////////////////////// 61 | func (opMethodBlacklist OpMethodBlacklist) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 62 | opScript := opData.OpScript[index] 63 | //////////////////////////////// 64 | if stateMap.StateTokenMap[opScript.Tick] == nil { 65 | opData.OpAccept = -1 66 | opData.OpError = "tick not found" 67 | return nil 68 | } 69 | if stateMap.StateTokenMap[opScript.Tick].Mod != "issue" { 70 | opData.OpAccept = -1 71 | opData.OpError = "mode invalid" 72 | return nil 73 | } 74 | if opScript.From != stateMap.StateTokenMap[opScript.Tick].To { 75 | opData.OpAccept = -1 76 | opData.OpError = "no ownership" 77 | return nil 78 | } 79 | if (opScript.To == stateMap.StateTokenMap[opScript.Tick].To || !misc.VerifyAddr(opScript.To, testnet)) { 80 | opData.OpAccept = -1 81 | opData.OpError = "address invalid" 82 | return nil 83 | } 84 | //////////////////////////////// 85 | keyBlacklist := opScript.Tick +"_"+ opScript.To 86 | opScript.Name = stateMap.StateTokenMap[opScript.Tick].Name 87 | //////////////////////////////// 88 | if (opScript.Mod == "add" && stateMap.StateBlacklistMap[keyBlacklist] != nil) { 89 | opData.OpAccept = -1 90 | opData.OpError = "no affected" 91 | return nil 92 | } 93 | if (opScript.Mod == "remove" && stateMap.StateBlacklistMap[keyBlacklist] == nil) { 94 | opData.OpAccept = -1 95 | opData.OpError = "no affected" 96 | return nil 97 | } 98 | //////////////////////////////// 99 | opData.StBefore = nil 100 | opData.StBefore = AppendStLineBlacklist(opData.StBefore, keyBlacklist, stateMap.StateBlacklistMap[keyBlacklist], false) 101 | //////////////////////////////// 102 | if opScript.Mod == "add" { 103 | stateMap.StateBlacklistMap[keyBlacklist] = &storage.StateBlacklistType{ 104 | Tick: opScript.Tick, 105 | Address: opScript.To, 106 | OpAdd: opData.OpScore, 107 | } 108 | } else if opScript.Mod == "remove" { 109 | stateMap.StateBlacklistMap[keyBlacklist] = nil 110 | } 111 | //////////////////////////////// 112 | opData.StAfter = nil 113 | opData.StAfter = AppendStLineBlacklist(opData.StAfter, keyBlacklist, stateMap.StateBlacklistMap[keyBlacklist], true) 114 | //////////////////////////////// 115 | opData.OpAccept = 1 116 | return nil 117 | } 118 | 119 | //////////////////////////////// 120 | /*func (opMethodBlacklist OpMethodBlacklist) UnDo() (error) { 121 | // ... 122 | return nil 123 | }*/ 124 | 125 | // ... 126 | -------------------------------------------------------------------------------- /operation/op_burn.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "math/big" 7 | "kasplex-executor/storage" 8 | ) 9 | 10 | //////////////////////////////// 11 | type OpMethodBurn struct {} 12 | 13 | //////////////////////////////// 14 | func init() { 15 | opName := "burn" 16 | P_Registered["KRC-20"] = true 17 | Op_Registered[opName] = true 18 | Method_Registered[opName] = new(OpMethodBurn) 19 | } 20 | 21 | //////////////////////////////// 22 | func (opMethodBurn OpMethodBurn) FeeLeast(daaScore uint64) (uint64) { 23 | // if daaScore ... 24 | return 0 25 | } 26 | 27 | //////////////////////////////// 28 | func (opMethodBurn OpMethodBurn) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) {} 29 | 30 | //////////////////////////////// 31 | func (opMethodBurn OpMethodBurn) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 32 | if (!testnet && daaScore < 110165000) { 33 | return false 34 | } 35 | if ValidateTxId(&script.Ca) { 36 | script.Tick = script.Ca 37 | } 38 | if (script.From == "" || script.P != "KRC-20" || !ValidateTickTxId(&script.Tick) || !ValidateAmount(&script.Amt)) { 39 | return false 40 | } 41 | script.To = "" 42 | script.Max = "" 43 | script.Lim = "" 44 | script.Pre = "" 45 | script.Dec = "" 46 | script.Utxo = "" 47 | script.Price = "" 48 | script.Mod = "" 49 | script.Name = "" 50 | script.Ca = "" 51 | return true 52 | } 53 | 54 | //////////////////////////////// 55 | func (opMethodBurn OpMethodBurn) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 56 | stateMap.StateTokenMap[opScript.Tick] = nil 57 | stateMap.StateBalanceMap[opScript.From+"_"+opScript.Tick] = nil 58 | } 59 | 60 | //////////////////////////////// 61 | func (opMethodBurn OpMethodBurn) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 62 | opScript := opData.OpScript[index] 63 | //////////////////////////////// 64 | if stateMap.StateTokenMap[opScript.Tick] == nil { 65 | opData.OpAccept = -1 66 | opData.OpError = "tick not found" 67 | return nil 68 | } 69 | if opScript.From != stateMap.StateTokenMap[opScript.Tick].To { 70 | opData.OpAccept = -1 71 | opData.OpError = "no ownership" 72 | return nil 73 | } 74 | //////////////////////////////// 75 | opScript.Name = stateMap.StateTokenMap[opScript.Tick].Name 76 | keyBalance := opScript.From +"_"+ opScript.Tick 77 | stToken := stateMap.StateTokenMap[opScript.Tick] 78 | stBalance := stateMap.StateBalanceMap[keyBalance] 79 | nTickAffc := int64(0) 80 | opScript.Name = stToken.Name 81 | //////////////////////////////// 82 | if stBalance == nil { 83 | opData.OpAccept = -1 84 | opData.OpError = "balance insuff" 85 | return nil 86 | } 87 | balanceBig := new(big.Int) 88 | balanceBig.SetString(stBalance.Balance, 10) 89 | amtBig := new(big.Int) 90 | amtBig.SetString(opScript.Amt, 10) 91 | if amtBig.Cmp(balanceBig) > 0 { 92 | opData.OpAccept = -1 93 | opData.OpError = "balance insuff" 94 | return nil 95 | } else if (amtBig.Cmp(balanceBig) == 0 && stBalance.Locked == "0") { 96 | nTickAffc = -1 97 | } 98 | //////////////////////////////// 99 | opData.StBefore = nil 100 | opData.StBefore = AppendStLineToken(opData.StBefore, opScript.Tick, stToken, false, false) 101 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalance, stBalance, false) 102 | //////////////////////////////// 103 | balanceBig = balanceBig.Sub(balanceBig, amtBig) 104 | stBalance.Balance = balanceBig.Text(10) 105 | stBalance.OpMod = opData.OpScore 106 | burnedBig := new(big.Int) 107 | burnedBig.SetString(stToken.Burned, 10) 108 | burnedBig = burnedBig.Add(burnedBig, amtBig) 109 | stToken.Burned = burnedBig.Text(10) 110 | stToken.OpMod = opData.OpScore 111 | stToken.MtsMod = opData.MtsAdd 112 | //////////////////////////////// 113 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, nTickAffc) 114 | lockedBig := new(big.Int) 115 | lockedBig.SetString(stBalance.Locked, 10) 116 | balanceBig = balanceBig.Add(balanceBig, lockedBig) 117 | balanceTotal := balanceBig.Text(10) 118 | opData.SsInfo.AddressAffc = AppendSsInfoAddressAffc(opData.SsInfo.AddressAffc, keyBalance, balanceTotal) 119 | //////////////////////////////// 120 | opData.StAfter = nil 121 | opData.StAfter = AppendStLineToken(opData.StAfter, opScript.Tick, stToken, false, true) 122 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalance, stBalance, true) 123 | //////////////////////////////// 124 | if (stBalance.Balance == "0" && stBalance.Locked == "0") { 125 | stateMap.StateBalanceMap[keyBalance] = nil 126 | } 127 | //////////////////////////////// 128 | opData.OpAccept = 1 129 | return nil 130 | } 131 | 132 | //////////////////////////////// 133 | /*func (opMethodBurn OpMethodBurn) UnDo() (error) { 134 | // ... 135 | return nil 136 | }*/ 137 | 138 | // ... 139 | -------------------------------------------------------------------------------- /operation/op_chown.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "kasplex-executor/misc" 7 | "kasplex-executor/storage" 8 | ) 9 | 10 | //////////////////////////////// 11 | type OpMethodChown struct {} 12 | 13 | //////////////////////////////// 14 | func init() { 15 | opName := "chown" 16 | P_Registered["KRC-20"] = true 17 | Op_Registered[opName] = true 18 | Method_Registered[opName] = new(OpMethodChown) 19 | } 20 | 21 | //////////////////////////////// 22 | func (opMethodChown OpMethodChown) FeeLeast(daaScore uint64) (uint64) { 23 | // if daaScore ... 24 | return 0 25 | } 26 | 27 | //////////////////////////////// 28 | func (opMethodChown OpMethodChown) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) {} 29 | 30 | //////////////////////////////// 31 | func (opMethodChown OpMethodChown) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 32 | if (!testnet && daaScore < 110165000) { 33 | return false 34 | } 35 | if ValidateTxId(&script.Ca) { 36 | script.Tick = script.Ca 37 | } 38 | if (script.From == "" || script.To == "" || script.P != "KRC-20" || !ValidateTickTxId(&script.Tick)) { 39 | return false 40 | } 41 | script.Amt = "" 42 | script.Max = "" 43 | script.Lim = "" 44 | script.Pre = "" 45 | script.Dec = "" 46 | script.Utxo = "" 47 | script.Price = "" 48 | script.Mod = "" 49 | script.Name = "" 50 | script.Ca = "" 51 | return true 52 | } 53 | 54 | //////////////////////////////// 55 | func (opMethodChown OpMethodChown) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 56 | stateMap.StateTokenMap[opScript.Tick] = nil 57 | } 58 | 59 | //////////////////////////////// 60 | func (opMethodChown OpMethodChown) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 61 | opScript := opData.OpScript[index] 62 | //////////////////////////////// 63 | if stateMap.StateTokenMap[opScript.Tick] == nil { 64 | opData.OpAccept = -1 65 | opData.OpError = "tick not found" 66 | return nil 67 | } 68 | if opScript.From != stateMap.StateTokenMap[opScript.Tick].To { 69 | opData.OpAccept = -1 70 | opData.OpError = "no ownership" 71 | return nil 72 | } 73 | if (opScript.To == stateMap.StateTokenMap[opScript.Tick].To || !misc.VerifyAddr(opScript.To, testnet)) { 74 | opData.OpAccept = -1 75 | opData.OpError = "address invalid" 76 | return nil 77 | } 78 | //////////////////////////////// 79 | stToken := stateMap.StateTokenMap[opScript.Tick] 80 | opScript.Name = stToken.Name 81 | //////////////////////////////// 82 | opData.StBefore = nil 83 | opData.StBefore = AppendStLineToken(opData.StBefore, opScript.Tick, stToken, true, false) 84 | //////////////////////////////// 85 | stToken.To = opScript.To 86 | stToken.OpMod = opData.OpScore 87 | stToken.MtsMod = opData.MtsAdd 88 | //////////////////////////////// 89 | opData.StAfter = nil 90 | opData.StAfter = AppendStLineToken(opData.StAfter, opScript.Tick, stToken, true, true) 91 | //////////////////////////////// 92 | opData.OpAccept = 1 93 | return nil 94 | } 95 | 96 | //////////////////////////////// 97 | /*func (opMethodChown OpMethodChown) UnDo() (error) { 98 | // ... 99 | return nil 100 | }*/ 101 | 102 | // ... 103 | -------------------------------------------------------------------------------- /operation/op_deploy.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "strconv" 7 | "math/big" 8 | "kasplex-executor/misc" 9 | "kasplex-executor/storage" 10 | ) 11 | 12 | //////////////////////////////// 13 | type OpMethodDeploy struct {} 14 | 15 | //////////////////////////////// 16 | func init() { 17 | opName := "deploy" 18 | P_Registered["KRC-20"] = true 19 | Op_Registered[opName] = true 20 | Method_Registered[opName] = new(OpMethodDeploy) 21 | } 22 | 23 | //////////////////////////////// 24 | func (opMethodDeploy OpMethodDeploy) FeeLeast(daaScore uint64) (uint64) { 25 | // if daaScore ... 26 | return 100000000000 27 | } 28 | 29 | //////////////////////////////// 30 | func (opMethodDeploy OpMethodDeploy) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) {} 31 | 32 | //////////////////////////////// 33 | func (opMethodDeploy OpMethodDeploy) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 34 | if ((testnet || daaScore >= 110165000) && script.Mod == "issue") { 35 | if (script.From == "" || script.P != "KRC-20" || !ValidateTick(&script.Name) || !ValidateDec(&script.Dec, "8")) { 36 | return false 37 | } 38 | if (script.Max != "0" && !ValidateAmount(&script.Max)) { 39 | return false 40 | } 41 | script.Tick = txId 42 | script.Lim = "0" 43 | } else { // mode mint 44 | if (script.From == "" || script.P != "KRC-20" || !ValidateTick(&script.Tick) || !ValidateAmount(&script.Max) || !ValidateAmount(&script.Lim) || !ValidateDec(&script.Dec, "8")) { 45 | return false 46 | } 47 | script.Mod = "" 48 | script.Name = "" 49 | } 50 | if !ValidateAmount(&script.Pre) { 51 | script.Pre = "0" 52 | } 53 | if script.To == "" { 54 | script.To = script.From 55 | } 56 | script.Amt = "" 57 | script.Utxo = "" 58 | script.Price = "" 59 | script.Ca = "" 60 | return true 61 | } 62 | 63 | //////////////////////////////// 64 | func (opMethodDeploy OpMethodDeploy) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 65 | stateMap.StateTokenMap[opScript.Tick] = nil 66 | if opScript.Pre != "0" { 67 | stateMap.StateBalanceMap[opScript.To+"_"+opScript.Tick] = nil 68 | } 69 | } 70 | 71 | //////////////////////////////// 72 | func (opMethodDeploy OpMethodDeploy) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 73 | opScript := opData.OpScript[index] 74 | //////////////////////////////// 75 | if stateMap.StateTokenMap[opScript.Tick] != nil { 76 | opData.OpAccept = -1 77 | opData.OpError = "tick existed" 78 | return nil 79 | } 80 | if TickIgnored[opScript.Tick] { 81 | opData.OpAccept = -1 82 | opData.OpError = "tick ignored" 83 | return nil 84 | } 85 | if (TickReserved[opScript.Tick] != "" && TickReserved[opScript.Tick] != opScript.From) { 86 | opData.OpAccept = -1 87 | opData.OpError = "tick reserved" 88 | return nil 89 | } 90 | if opData.Fee == 0 { 91 | opData.OpAccept = -1 92 | opData.OpError = "fee unknown" 93 | return nil 94 | } 95 | if opData.Fee < opData.FeeLeast { 96 | opData.OpAccept = -1 97 | opData.OpError = "fee not enough" 98 | return nil 99 | } 100 | if (opScript.Pre != "0" && !misc.VerifyAddr(opScript.To, testnet)) { 101 | opData.OpAccept = -1 102 | opData.OpError = "address invalid" 103 | return nil 104 | } 105 | //////////////////////////////// 106 | keyBalance := opScript.To +"_"+ opScript.Tick 107 | stToken := stateMap.StateTokenMap[opScript.Tick] 108 | stBalance := stateMap.StateBalanceMap[keyBalance] 109 | //////////////////////////////// 110 | opData.StBefore = nil 111 | opData.StBefore = AppendStLineToken(opData.StBefore, opScript.Tick, stToken, true, false) 112 | if opScript.Pre != "0" { 113 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalance, stBalance, false) 114 | } 115 | //////////////////////////////// 116 | decInt, _ := strconv.Atoi(opScript.Dec) 117 | minted := "0" 118 | burned := "0" 119 | stToken = &storage.StateTokenType{ 120 | Tick: opScript.Tick, 121 | Max: opScript.Max, 122 | Lim: opScript.Lim, 123 | Pre: opScript.Pre, 124 | Dec: decInt, 125 | Mod: opScript.Mod, 126 | From: opScript.From, 127 | To: opScript.To, 128 | Minted: minted, 129 | Burned: burned, 130 | TxId: opData.TxId, 131 | OpAdd: opData.OpScore, 132 | OpMod: opData.OpScore, 133 | MtsAdd: opData.MtsAdd, 134 | MtsMod: opData.MtsAdd, 135 | } 136 | if opScript.Mod == "issue" { 137 | stToken.Name = opScript.Name 138 | } 139 | stateMap.StateTokenMap[opScript.Tick] = stToken 140 | if opScript.Pre != "0" { 141 | minted = opScript.Pre 142 | if opScript.Max != "0" { 143 | maxBig := new(big.Int) 144 | maxBig.SetString(opScript.Max, 10) 145 | preBig := new(big.Int) 146 | preBig.SetString(opScript.Pre, 10) 147 | if preBig.Cmp(maxBig) > 0 { 148 | minted = opScript.Max 149 | } 150 | } 151 | stToken.Minted = minted 152 | stBalance = &storage.StateBalanceType{ 153 | Address: opScript.To, 154 | Tick: opScript.Tick, 155 | Dec: decInt, 156 | Balance: minted, 157 | Locked: "0", 158 | OpMod: opData.OpScore, 159 | } 160 | stateMap.StateBalanceMap[keyBalance] = stBalance 161 | //////////////////////////// 162 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, 1) 163 | opData.SsInfo.AddressAffc = AppendSsInfoAddressAffc(opData.SsInfo.AddressAffc, keyBalance, minted) 164 | } else { 165 | //////////////////////////// 166 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, 0) 167 | } 168 | //////////////////////////////// 169 | opData.StAfter = nil 170 | opData.StAfter = AppendStLineToken(opData.StAfter, opScript.Tick, stToken, true, true) 171 | if opScript.Pre != "0" { 172 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalance, stBalance, true) 173 | } 174 | //////////////////////////////// 175 | opData.OpAccept = 1 176 | return nil 177 | } 178 | 179 | //////////////////////////////// 180 | /*func (opMethodDeploy OpMethodDeploy) UnDo() (error) { 181 | // ... 182 | return nil 183 | }*/ 184 | 185 | // ... 186 | -------------------------------------------------------------------------------- /operation/op_issue.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "math/big" 7 | "kasplex-executor/misc" 8 | "kasplex-executor/storage" 9 | ) 10 | 11 | //////////////////////////////// 12 | type OpMethodIssue struct {} 13 | 14 | //////////////////////////////// 15 | func init() { 16 | opName := "issue" 17 | P_Registered["KRC-20"] = true 18 | Op_Registered[opName] = true 19 | Method_Registered[opName] = new(OpMethodIssue) 20 | } 21 | 22 | //////////////////////////////// 23 | func (opMethodIssue OpMethodIssue) FeeLeast(daaScore uint64) (uint64) { 24 | // if daaScore ... 25 | return 0 26 | } 27 | 28 | //////////////////////////////// 29 | func (opMethodIssue OpMethodIssue) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) {} 30 | 31 | //////////////////////////////// 32 | func (opMethodIssue OpMethodIssue) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 33 | if (!testnet && daaScore < 110165000) { 34 | return false 35 | } 36 | if (script.From == "" || script.P != "KRC-20" || !ValidateTxId(&script.Ca) || !ValidateAmount(&script.Amt)) { 37 | return false 38 | } 39 | if script.To == "" { 40 | script.To = script.From 41 | } 42 | script.Tick = script.Ca 43 | script.Max = "" 44 | script.Lim = "" 45 | script.Pre = "" 46 | script.Dec = "" 47 | script.Utxo = "" 48 | script.Price = "" 49 | script.Mod = "" 50 | script.Name = "" 51 | script.Ca = "" 52 | return true 53 | } 54 | 55 | //////////////////////////////// 56 | func (opMethodIssue OpMethodIssue) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 57 | stateMap.StateTokenMap[opScript.Tick] = nil 58 | stateMap.StateBalanceMap[opScript.To+"_"+opScript.Tick] = nil 59 | } 60 | 61 | //////////////////////////////// 62 | func (opMethodIssue OpMethodIssue) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 63 | opScript := opData.OpScript[index] 64 | //////////////////////////////// 65 | if stateMap.StateTokenMap[opScript.Tick] == nil { 66 | opData.OpAccept = -1 67 | opData.OpError = "tick not found" 68 | return nil 69 | } 70 | if stateMap.StateTokenMap[opScript.Tick].Mod != "issue" { 71 | opData.OpAccept = -1 72 | opData.OpError = "mode invalid" 73 | return nil 74 | } 75 | if opScript.From != stateMap.StateTokenMap[opScript.Tick].To { 76 | opData.OpAccept = -1 77 | opData.OpError = "no ownership" 78 | return nil 79 | } 80 | if !misc.VerifyAddr(opScript.To, testnet) { 81 | opData.OpAccept = -1 82 | opData.OpError = "address invalid" 83 | return nil 84 | } 85 | //////////////////////////////// 86 | keyBalance := opScript.To +"_"+ opScript.Tick 87 | stToken := stateMap.StateTokenMap[opScript.Tick] 88 | stBalance := stateMap.StateBalanceMap[keyBalance] 89 | opScript.Name = stToken.Name 90 | //////////////////////////////// 91 | amt := opScript.Amt 92 | mintedBig := new(big.Int) 93 | mintedBig.SetString(stToken.Minted, 10) 94 | limBig := new(big.Int) 95 | if stToken.Max != "0" { 96 | maxBig := new(big.Int) 97 | maxBig.SetString(stToken.Max, 10) 98 | leftBig := maxBig.Sub(maxBig, mintedBig) 99 | limBig.SetString("0", 10) 100 | if limBig.Cmp(leftBig) >= 0 { 101 | opData.OpAccept = -1 102 | opData.OpError = "issue finished" 103 | return nil 104 | } 105 | limBig.SetString(amt, 10) 106 | if limBig.Cmp(leftBig) > 0 { 107 | amt = leftBig.Text(10) 108 | } 109 | opScript.Amt = amt 110 | } 111 | limBig.SetString(amt, 10) 112 | mintedBig = mintedBig.Add(mintedBig, limBig) 113 | minted := mintedBig.Text(10) 114 | //////////////////////////////// 115 | opData.StBefore = nil 116 | opData.StBefore = AppendStLineToken(opData.StBefore, opScript.Tick, stToken, false, false) 117 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalance, stBalance, false) 118 | //////////////////////////////// 119 | stToken.Minted = minted 120 | stToken.OpMod = opData.OpScore 121 | stToken.MtsMod = opData.MtsAdd 122 | if stBalance == nil { 123 | stBalance = &storage.StateBalanceType{ 124 | Address: opScript.To, 125 | Tick: opScript.Tick, 126 | Dec: stToken.Dec, 127 | Balance: "0", 128 | Locked: "0", 129 | OpMod: opData.OpScore, 130 | } 131 | stateMap.StateBalanceMap[keyBalance] = stBalance 132 | //////////////////////////// 133 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, 1) 134 | } else { 135 | //////////////////////////// 136 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, 0) 137 | } 138 | mintedBig.SetString(stBalance.Balance, 10) 139 | mintedBig = mintedBig.Add(mintedBig, limBig) 140 | stBalance.Balance = mintedBig.Text(10) 141 | stBalance.OpMod = opData.OpScore 142 | //////////////////////////////// 143 | lockedBig := new(big.Int) 144 | lockedBig.SetString(stBalance.Locked, 10) 145 | mintedBig = mintedBig.Add(mintedBig, lockedBig) 146 | balanceTotal := mintedBig.Text(10) 147 | opData.SsInfo.AddressAffc = AppendSsInfoAddressAffc(opData.SsInfo.AddressAffc, keyBalance, balanceTotal) 148 | //////////////////////////////// 149 | opData.StAfter = nil 150 | opData.StAfter = AppendStLineToken(opData.StAfter, opScript.Tick, stToken, false, true) 151 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalance, stBalance, true) 152 | //////////////////////////////// 153 | opData.OpAccept = 1 154 | return nil 155 | } 156 | 157 | //////////////////////////////// 158 | /*func (opMethodIssue OpMethodIssue) UnDo() (error) { 159 | // ... 160 | return nil 161 | }*/ 162 | 163 | // ... 164 | -------------------------------------------------------------------------------- /operation/op_list.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "strings" 7 | "strconv" 8 | "math/big" 9 | "kasplex-executor/misc" 10 | "kasplex-executor/storage" 11 | ) 12 | 13 | //////////////////////////////// 14 | type OpMethodList struct {} 15 | 16 | //////////////////////////////// 17 | func init() { 18 | opName := "list" 19 | P_Registered["KRC-20"] = true 20 | Op_Registered[opName] = true 21 | Method_Registered[opName] = new(OpMethodList) 22 | } 23 | 24 | //////////////////////////////// 25 | func (opMethodList OpMethodList) FeeLeast(daaScore uint64) (uint64) { 26 | return 0 27 | } 28 | 29 | //////////////////////////////// 30 | func (opMethodList OpMethodList) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) { 31 | script.Utxo = "" 32 | if len(txData.Data.Outputs) > 0 { 33 | script.Utxo = txData.TxId + "_" + txData.Data.Outputs[0].VerboseData.ScriptPublicKeyAddress + "_" + strconv.FormatUint(txData.Data.Outputs[0].Amount,10) 34 | } 35 | } 36 | 37 | //////////////////////////////// 38 | func (opMethodList OpMethodList) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 39 | if (!testnet && daaScore < 97539090) { 40 | return false 41 | } 42 | if ValidateTxId(&script.Ca) { 43 | script.Tick = script.Ca 44 | } 45 | if (script.From == "" || script.Utxo == "" || script.P != "KRC-20" || !ValidateTickTxId(&script.Tick) || !ValidateAmount(&script.Amt)) { 46 | return false 47 | } 48 | script.To = "" 49 | script.Max = "" 50 | script.Lim = "" 51 | script.Pre = "" 52 | script.Dec = "" 53 | script.Price = "" 54 | script.Mod = "" 55 | script.Name = "" 56 | script.Ca = "" 57 | return true 58 | } 59 | 60 | //////////////////////////////// 61 | func (opMethodList OpMethodList) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 62 | stateMap.StateTokenMap[opScript.Tick] = nil 63 | stateMap.StateBalanceMap[opScript.From+"_"+opScript.Tick] = nil 64 | stateMap.StateBlacklistMap[opScript.Tick+"_"+opScript.From] = nil 65 | } 66 | 67 | //////////////////////////////// 68 | func (opMethodList OpMethodList) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 69 | opScript := opData.OpScript[index] 70 | //////////////////////////////// 71 | if stateMap.StateTokenMap[opScript.Tick] == nil { 72 | opData.OpAccept = -1 73 | opData.OpError = "tick not found" 74 | return nil 75 | } 76 | if stateMap.StateBlacklistMap[opScript.Tick+"_"+opScript.From] != nil { 77 | opData.OpAccept = -1 78 | opData.OpError = "blacklist" 79 | return nil 80 | } 81 | //////////////////////////////// 82 | dataUtxo := strings.Split(opScript.Utxo, "_") 83 | keyMarket := opScript.Tick +"_"+ opScript.From +"_"+ dataUtxo[0] 84 | keyBalance := opScript.From +"_"+ opScript.Tick 85 | stBalance := stateMap.StateBalanceMap[keyBalance] 86 | opScript.Name = stateMap.StateTokenMap[opScript.Tick].Name 87 | //////////////////////////////// 88 | if stBalance == nil { 89 | opData.OpAccept = -1 90 | opData.OpError = "balance insuff" 91 | return nil 92 | } 93 | balanceBig := new(big.Int) 94 | balanceBig.SetString(stBalance.Balance, 10) 95 | amtBig := new(big.Int) 96 | amtBig.SetString(opScript.Amt, 10) 97 | if amtBig.Cmp(balanceBig) > 0 { 98 | opData.OpAccept = -1 99 | opData.OpError = "balance insuff" 100 | return nil 101 | } 102 | uScript := "" 103 | uJson1 := `{"p":"krc-20","op":"send","tick":"` + strings.ToLower(opScript.Tick) + `"}` 104 | uJson2 := `{"p":"krc-20","op":"send","ca":"` + strings.ToLower(opScript.Tick) + `"}` 105 | uAddr1, uScript1 := misc.MakeP2shKasplex(opData.ScriptSig, "", uJson1, testnet) 106 | uAddr2, uScript2 := misc.MakeP2shKasplex(opData.ScriptSig, "", uJson2, testnet) 107 | if dataUtxo[1] == uAddr1 { 108 | uScript = uScript1 109 | } else if (dataUtxo[1] == uAddr2 && stateMap.StateTokenMap[opScript.Tick].Mod == "issue") { 110 | uScript = uScript2 111 | } else { 112 | opData.OpAccept = -1 113 | opData.OpError = "address invalid" 114 | return nil 115 | } 116 | //////////////////////////////// 117 | opData.StBefore = nil 118 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalance, stBalance, false) 119 | opData.StBefore = AppendStLineMarket(opData.StBefore, keyMarket, nil, false) 120 | //////////////////////////////// 121 | balanceBig = balanceBig.Sub(balanceBig, amtBig) 122 | lockedBig := new(big.Int) 123 | lockedBig.SetString(stBalance.Locked, 10) 124 | lockedBig = lockedBig.Add(lockedBig, amtBig) 125 | stBalance.Balance = balanceBig.Text(10) 126 | stBalance.Locked = lockedBig.Text(10) 127 | stBalance.OpMod = opData.OpScore 128 | stMarket := &storage.StateMarketType{ 129 | Tick: opScript.Tick, 130 | TAmt: opScript.Amt, 131 | TAddr: opScript.From, 132 | UTxId: dataUtxo[0], 133 | UAddr: dataUtxo[1], 134 | UAmt: dataUtxo[2], 135 | UScript: uScript, 136 | OpAdd: opData.OpScore, 137 | } 138 | stateMap.StateMarketMap[keyMarket] = stMarket 139 | //////////////////////////////// 140 | opData.StAfter = nil 141 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalance, stBalance, true) 142 | opData.StAfter = AppendStLineMarket(opData.StAfter, keyMarket, stMarket, true) 143 | //////////////////////////////// 144 | opData.OpAccept = 1 145 | return nil 146 | } 147 | 148 | //////////////////////////////// 149 | /*func (opMethodList OpMethodList) UnDo() (error) { 150 | // ... 151 | return nil 152 | }*/ 153 | 154 | // ... 155 | -------------------------------------------------------------------------------- /operation/op_mint.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "math/big" 7 | "kasplex-executor/misc" 8 | "kasplex-executor/storage" 9 | ) 10 | 11 | //////////////////////////////// 12 | type OpMethodMint struct {} 13 | 14 | //////////////////////////////// 15 | func init() { 16 | opName := "mint" 17 | P_Registered["KRC-20"] = true 18 | Op_Registered[opName] = true 19 | Method_Registered[opName] = new(OpMethodMint) 20 | } 21 | 22 | //////////////////////////////// 23 | func (opMethodMint OpMethodMint) FeeLeast(daaScore uint64) (uint64) { 24 | // if daaScore ... 25 | return 100000000 26 | } 27 | 28 | //////////////////////////////// 29 | func (opMethodMint OpMethodMint) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) {} 30 | 31 | //////////////////////////////// 32 | func (opMethodMint OpMethodMint) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 33 | if (script.From == "" || script.P != "KRC-20" || !ValidateTick(&script.Tick)) { 34 | return false 35 | } 36 | script.Amt = "" 37 | if script.To == "" { 38 | script.To = script.From 39 | } 40 | script.Max = "" 41 | script.Lim = "" 42 | script.Pre = "" 43 | script.Dec = "" 44 | script.Utxo = "" 45 | script.Price = "" 46 | script.Mod = "" 47 | script.Name = "" 48 | script.Ca = "" 49 | return true 50 | } 51 | 52 | //////////////////////////////// 53 | func (opMethodMint OpMethodMint) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 54 | stateMap.StateTokenMap[opScript.Tick] = nil 55 | stateMap.StateBalanceMap[opScript.To+"_"+opScript.Tick] = nil 56 | } 57 | 58 | //////////////////////////////// 59 | func (opMethodMint OpMethodMint) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 60 | opScript := opData.OpScript[index] 61 | //////////////////////////////// 62 | if stateMap.StateTokenMap[opScript.Tick] == nil { 63 | opData.OpAccept = -1 64 | opData.OpError = "tick not found" 65 | return nil 66 | } 67 | if stateMap.StateTokenMap[opScript.Tick].Mod != "" { 68 | opData.OpAccept = -1 69 | opData.OpError = "mode invalid" 70 | return nil 71 | } 72 | if opData.Fee == 0 { 73 | opData.OpAccept = -1 74 | opData.OpError = "fee unknown" 75 | return nil 76 | } 77 | if opData.Fee < opData.FeeLeast { 78 | opData.OpAccept = -1 79 | opData.OpError = "fee not enough" 80 | return nil 81 | } 82 | if !misc.VerifyAddr(opScript.To, testnet) { 83 | opData.OpAccept = -1 84 | opData.OpError = "address invalid" 85 | return nil 86 | } 87 | //////////////////////////////// 88 | keyBalance := opScript.To +"_"+ opScript.Tick 89 | stToken := stateMap.StateTokenMap[opScript.Tick] 90 | stBalance := stateMap.StateBalanceMap[keyBalance] 91 | //////////////////////////////// 92 | amt := stToken.Lim 93 | maxBig := new(big.Int) 94 | maxBig.SetString(stToken.Max, 10) 95 | mintedBig := new(big.Int) 96 | mintedBig.SetString(stToken.Minted, 10) 97 | leftBig := maxBig.Sub(maxBig, mintedBig) 98 | limBig := new(big.Int) 99 | limBig.SetString("0", 10) 100 | if limBig.Cmp(leftBig) >= 0 { 101 | opData.OpAccept = -1 102 | opData.OpError = "mint finished" 103 | return nil 104 | } 105 | limBig.SetString(amt, 10) 106 | if limBig.Cmp(leftBig) > 0 { 107 | amt = leftBig.Text(10) 108 | } 109 | opScript.Amt = amt 110 | limBig.SetString(amt, 10) 111 | mintedBig = mintedBig.Add(mintedBig, limBig) 112 | minted := mintedBig.Text(10) 113 | //////////////////////////////// 114 | opData.StBefore = nil 115 | opData.StBefore = AppendStLineToken(opData.StBefore, opScript.Tick, stToken, false, false) 116 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalance, stBalance, false) 117 | //////////////////////////////// 118 | stToken.Minted = minted 119 | stToken.OpMod = opData.OpScore 120 | stToken.MtsMod = opData.MtsAdd 121 | if stBalance == nil { 122 | stBalance = &storage.StateBalanceType{ 123 | Address: opScript.To, 124 | Tick: opScript.Tick, 125 | Dec: stToken.Dec, 126 | Balance: "0", 127 | Locked: "0", 128 | OpMod: opData.OpScore, 129 | } 130 | stateMap.StateBalanceMap[keyBalance] = stBalance 131 | //////////////////////////// 132 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, 1) 133 | } else { 134 | //////////////////////////// 135 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, 0) 136 | } 137 | mintedBig.SetString(stBalance.Balance, 10) 138 | mintedBig = mintedBig.Add(mintedBig, limBig) 139 | stBalance.Balance = mintedBig.Text(10) 140 | stBalance.OpMod = opData.OpScore 141 | //////////////////////////////// 142 | lockedBig := new(big.Int) 143 | lockedBig.SetString(stBalance.Locked, 10) 144 | mintedBig = mintedBig.Add(mintedBig, lockedBig) 145 | balanceTotal := mintedBig.Text(10) 146 | opData.SsInfo.AddressAffc = AppendSsInfoAddressAffc(opData.SsInfo.AddressAffc, keyBalance, balanceTotal) 147 | //////////////////////////////// 148 | opData.StAfter = nil 149 | opData.StAfter = AppendStLineToken(opData.StAfter, opScript.Tick, stToken, false, true) 150 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalance, stBalance, true) 151 | //////////////////////////////// 152 | opData.OpAccept = 1 153 | return nil 154 | } 155 | 156 | //////////////////////////////// 157 | /*func (opMethodMint OpMethodMint) UnDo() (error) { 158 | // ... 159 | return nil 160 | }*/ 161 | 162 | // ... 163 | -------------------------------------------------------------------------------- /operation/op_send.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | "strings" 7 | "strconv" 8 | "math/big" 9 | "kasplex-executor/storage" 10 | ) 11 | 12 | //////////////////////////////// 13 | type OpMethodSend struct {} 14 | 15 | //////////////////////////////// 16 | func init() { 17 | opName := "send" 18 | P_Registered["KRC-20"] = true 19 | Op_Registered[opName] = true 20 | OpRecycle_Registered[opName] = true 21 | Method_Registered[opName] = new(OpMethodSend) 22 | } 23 | 24 | //////////////////////////////// 25 | func (opMethodSend OpMethodSend) FeeLeast(daaScore uint64) (uint64) { 26 | return 0 27 | } 28 | 29 | //////////////////////////////// 30 | func (opMethodSend OpMethodSend) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) { 31 | script.Utxo = "" 32 | script.Price = "" 33 | script.Utxo = txData.Data.Inputs[index].PreviousOutpoint.TransactionId + "_" + script.From 34 | if len(txData.Data.Outputs) > 0 { 35 | script.Price = strconv.FormatUint(txData.Data.Outputs[0].Amount, 10) 36 | } 37 | if (len(txData.Data.Outputs) > 1 && index == 0) { 38 | script.To = txData.Data.Outputs[1].VerboseData.ScriptPublicKeyAddress 39 | } else { 40 | script.Price = "0" 41 | script.To = script.From 42 | } 43 | } 44 | 45 | //////////////////////////////// 46 | func (opMethodSend OpMethodSend) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 47 | if (!testnet && daaScore < 97539090) { 48 | return false 49 | } 50 | if ValidateTxId(&script.Ca) { 51 | script.Tick = script.Ca 52 | } 53 | if (script.From == "" || script.To == "" || script.Utxo == "" || script.P != "KRC-20" || !ValidateTickTxId(&script.Tick)) { 54 | return false 55 | } 56 | script.Amt = "" 57 | script.Max = "" 58 | script.Lim = "" 59 | script.Pre = "" 60 | script.Dec = "" 61 | script.Mod = "" 62 | script.Name = "" 63 | script.Ca = "" 64 | return true 65 | } 66 | 67 | //////////////////////////////// 68 | func (opMethodSend OpMethodSend) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 69 | stateMap.StateTokenMap[opScript.Tick] = nil 70 | stateMap.StateBalanceMap[opScript.From+"_"+opScript.Tick] = nil 71 | stateMap.StateBalanceMap[opScript.To+"_"+opScript.Tick] = nil 72 | dataUtxo := strings.Split(opScript.Utxo, "_") 73 | stateMap.StateMarketMap[opScript.Tick+"_"+opScript.From+"_"+dataUtxo[0]] = nil 74 | } 75 | 76 | //////////////////////////////// 77 | func (opMethodSend OpMethodSend) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 78 | opScript := opData.OpScript[index] 79 | //////////////////////////////// 80 | if stateMap.StateTokenMap[opScript.Tick] == nil { 81 | opData.OpAccept = -1 82 | opData.OpError = "tick not found" 83 | return nil 84 | } 85 | //////////////////////////////// 86 | dataUtxo := strings.Split(opScript.Utxo, "_") 87 | keyMarket := opScript.Tick +"_"+ opScript.From +"_"+ dataUtxo[0] 88 | keyBalanceFrom := opScript.From +"_"+ opScript.Tick 89 | keyBalanceTo := opScript.To +"_"+ opScript.Tick 90 | stMarket := stateMap.StateMarketMap[keyMarket] 91 | stBalanceFrom := stateMap.StateBalanceMap[keyBalanceFrom] 92 | stBalanceTo := stateMap.StateBalanceMap[keyBalanceTo] 93 | nTickAffc := int64(0) 94 | opScript.Name = stateMap.StateTokenMap[opScript.Tick].Name 95 | //////////////////////////////// 96 | if stMarket == nil { 97 | opData.OpAccept = -1 98 | opData.OpError = "order not found" 99 | return nil 100 | } 101 | if stBalanceFrom == nil { 102 | opData.OpAccept = -1 103 | opData.OpError = "order abnormal" 104 | return nil 105 | } 106 | opScript.Amt = stMarket.TAmt 107 | amtBig := new(big.Int) 108 | amtBig.SetString(opScript.Amt, 10) 109 | lockedBig := new(big.Int) 110 | lockedBig.SetString(stBalanceFrom.Locked, 10) 111 | if amtBig.Cmp(lockedBig) > 0 { 112 | opData.OpAccept = -1 113 | opData.OpError = "order abnormal" 114 | return nil 115 | } 116 | //////////////////////////////// 117 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalanceFrom, stBalanceFrom, false) 118 | if keyBalanceFrom != keyBalanceTo { 119 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalanceTo, stBalanceTo, false) 120 | } 121 | opData.StBefore = AppendStLineMarket(opData.StBefore, keyMarket, stMarket, false) 122 | //////////////////////////////// 123 | if stBalanceTo == nil { 124 | stBalanceTo = &storage.StateBalanceType{ 125 | Address: opScript.To, 126 | Tick: opScript.Tick, 127 | Dec: stBalanceFrom.Dec, 128 | Balance: "0", 129 | Locked: "0", 130 | OpMod: opData.OpScore, 131 | } 132 | stateMap.StateBalanceMap[keyBalanceTo] = stBalanceTo 133 | nTickAffc ++ 134 | } 135 | lockedBig = lockedBig.Sub(lockedBig, amtBig) 136 | stBalanceFrom.Locked = lockedBig.Text(10) 137 | balanceBig := new(big.Int) 138 | balanceBig.SetString(stBalanceTo.Balance, 10) 139 | balanceBig = balanceBig.Add(balanceBig, amtBig) 140 | stBalanceTo.Balance = balanceBig.Text(10) 141 | if (stBalanceFrom.Balance == "0" && stBalanceFrom.Locked == "0") { 142 | nTickAffc -- 143 | } 144 | stateMap.StateMarketMap[keyMarket] = nil 145 | //////////////////////////////// 146 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, nTickAffc) 147 | balanceBig.SetString(stBalanceFrom.Balance, 10) 148 | balanceBig = balanceBig.Add(balanceBig, lockedBig) 149 | opData.SsInfo.AddressAffc = AppendSsInfoAddressAffc(opData.SsInfo.AddressAffc, keyBalanceFrom, balanceBig.Text(10)) 150 | if keyBalanceFrom != keyBalanceTo { 151 | balanceBig.SetString(stBalanceTo.Balance, 10) 152 | lockedBig.SetString(stBalanceTo.Locked, 10) 153 | balanceBig = balanceBig.Add(balanceBig, lockedBig) 154 | opData.SsInfo.AddressAffc = AppendSsInfoAddressAffc(opData.SsInfo.AddressAffc, keyBalanceTo, balanceBig.Text(10)) 155 | } 156 | //////////////////////////////// 157 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalanceFrom, stBalanceFrom, true) 158 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalanceTo, stBalanceTo, true) 159 | opData.StAfter = AppendStLineMarket(opData.StAfter, keyMarket, nil, true) 160 | //////////////////////////////// 161 | if (stBalanceFrom.Balance == "0" && stBalanceFrom.Locked == "0") { 162 | stateMap.StateBalanceMap[keyBalanceFrom] = nil 163 | } 164 | //////////////////////////////// 165 | opData.OpAccept = 1 166 | return nil 167 | } 168 | 169 | //////////////////////////////// 170 | /*func (opMethodSend OpMethodSend) UnDo() (error) { 171 | // ... 172 | return nil 173 | }*/ 174 | 175 | // ... 176 | 177 | -------------------------------------------------------------------------------- /operation/op_transfer.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package operation 4 | 5 | import ( 6 | //"strconv" 7 | "math/big" 8 | "kasplex-executor/misc" 9 | "kasplex-executor/storage" 10 | ) 11 | 12 | //////////////////////////////// 13 | type OpMethodTransfer struct {} 14 | 15 | //////////////////////////////// 16 | func init() { 17 | opName := "transfer" 18 | P_Registered["KRC-20"] = true 19 | Op_Registered[opName] = true 20 | Method_Registered[opName] = new(OpMethodTransfer) 21 | } 22 | 23 | //////////////////////////////// 24 | func (opMethodTransfer OpMethodTransfer) FeeLeast(daaScore uint64) (uint64) { 25 | return 0 26 | } 27 | 28 | //////////////////////////////// 29 | func (opMethodTransfer OpMethodTransfer) ScriptCollectEx(index int, script *storage.DataScriptType, txData *storage.DataTransactionType, testnet bool) {} 30 | 31 | //////////////////////////////// 32 | func (opMethodTransfer OpMethodTransfer) Validate(script *storage.DataScriptType, txId string, daaScore uint64, testnet bool) (bool) { 33 | if ValidateTxId(&script.Ca) { 34 | script.Tick = script.Ca 35 | } 36 | if (script.From == "" || script.To == "" || script.P != "KRC-20" || !ValidateTickTxId(&script.Tick) || !ValidateAmount(&script.Amt)) { 37 | return false 38 | } 39 | script.Max = "" 40 | script.Lim = "" 41 | script.Pre = "" 42 | script.Dec = "" 43 | script.Utxo = "" 44 | script.Price = "" 45 | script.Mod = "" 46 | script.Name = "" 47 | script.Ca = "" 48 | return true 49 | } 50 | 51 | //////////////////////////////// 52 | func (opMethodTransfer OpMethodTransfer) PrepareStateKey(opScript *storage.DataScriptType, stateMap storage.DataStateMapType) { 53 | stateMap.StateTokenMap[opScript.Tick] = nil 54 | stateMap.StateBalanceMap[opScript.From+"_"+opScript.Tick] = nil 55 | stateMap.StateBalanceMap[opScript.To+"_"+opScript.Tick] = nil 56 | stateMap.StateBlacklistMap[opScript.Tick+"_"+opScript.From] = nil 57 | } 58 | 59 | //////////////////////////////// 60 | func (opMethodTransfer OpMethodTransfer) Do(index int, opData *storage.DataOperationType, stateMap storage.DataStateMapType, testnet bool) (error) { 61 | opScript := opData.OpScript[index] 62 | //////////////////////////////// 63 | if stateMap.StateTokenMap[opScript.Tick] == nil { 64 | opData.OpAccept = -1 65 | opData.OpError = "tick not found" 66 | return nil 67 | } 68 | if stateMap.StateBlacklistMap[opScript.Tick+"_"+opScript.From] != nil { 69 | opData.OpAccept = -1 70 | opData.OpError = "blacklist" 71 | return nil 72 | } 73 | if (opScript.From == opScript.To || !misc.VerifyAddr(opScript.To, testnet)) { 74 | opData.OpAccept = -1 75 | opData.OpError = "address invalid" 76 | return nil 77 | } 78 | //////////////////////////////// 79 | keyBalanceFrom := opScript.From +"_"+ opScript.Tick 80 | keyBalanceTo := opScript.To +"_"+ opScript.Tick 81 | stBalanceFrom := stateMap.StateBalanceMap[keyBalanceFrom] 82 | stBalanceTo := stateMap.StateBalanceMap[keyBalanceTo] 83 | nTickAffc := int64(0) 84 | opScript.Name = stateMap.StateTokenMap[opScript.Tick].Name 85 | //////////////////////////////// 86 | if stBalanceFrom == nil { 87 | opData.OpAccept = -1 88 | opData.OpError = "balance insuff" 89 | return nil 90 | } 91 | balanceBig := new(big.Int) 92 | balanceBig.SetString(stBalanceFrom.Balance, 10) 93 | amtBig := new(big.Int) 94 | amtBig.SetString(opScript.Amt, 10) 95 | if amtBig.Cmp(balanceBig) > 0 { 96 | opData.OpAccept = -1 97 | opData.OpError = "balance insuff" 98 | return nil 99 | } else if (amtBig.Cmp(balanceBig) == 0 && stBalanceFrom.Locked == "0") { 100 | nTickAffc = -1 101 | } 102 | //////////////////////////////// 103 | opData.StBefore = nil 104 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalanceFrom, stBalanceFrom, false) 105 | opData.StBefore = AppendStLineBalance(opData.StBefore, keyBalanceTo, stBalanceTo, false) 106 | //////////////////////////////// 107 | balanceBig = balanceBig.Sub(balanceBig, amtBig) 108 | stBalanceFrom.Balance = balanceBig.Text(10) 109 | stBalanceFrom.OpMod = opData.OpScore 110 | lockedBig := new(big.Int) 111 | lockedBig.SetString(stBalanceFrom.Locked, 10) 112 | balanceBig = balanceBig.Add(balanceBig, lockedBig) 113 | balanceFromTotal := balanceBig.Text(10) 114 | if stBalanceTo == nil { 115 | stBalanceTo = &storage.StateBalanceType{ 116 | Address: opScript.To, 117 | Tick: opScript.Tick, 118 | Dec: stBalanceFrom.Dec, 119 | Balance: "0", 120 | Locked: "0", 121 | OpMod: opData.OpScore, 122 | } 123 | stateMap.StateBalanceMap[keyBalanceTo] = stBalanceTo 124 | nTickAffc ++ 125 | } 126 | balanceBig.SetString(stBalanceTo.Balance, 10) 127 | balanceBig = balanceBig.Add(balanceBig, amtBig) 128 | stBalanceTo.Balance = balanceBig.Text(10) 129 | stBalanceTo.OpMod = opData.OpScore 130 | lockedBig.SetString(stBalanceTo.Locked, 10) 131 | balanceBig = balanceBig.Add(balanceBig, lockedBig) 132 | balanceToTotal := balanceBig.Text(10) 133 | //////////////////////////////// 134 | opData.SsInfo.TickAffc = AppendSsInfoTickAffc(opData.SsInfo.TickAffc, opScript.Tick, nTickAffc) 135 | opData.SsInfo.AddressAffc = AppendSsInfoAddressAffc(opData.SsInfo.AddressAffc, keyBalanceFrom, balanceFromTotal) 136 | opData.SsInfo.AddressAffc = AppendSsInfoAddressAffc(opData.SsInfo.AddressAffc, keyBalanceTo, balanceToTotal) 137 | //////////////////////////////// 138 | opData.StAfter = nil 139 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalanceFrom, stBalanceFrom, true) 140 | opData.StAfter = AppendStLineBalance(opData.StAfter, keyBalanceTo, stBalanceTo, true) 141 | //////////////////////////////// 142 | if (stBalanceFrom.Balance == "0" && stBalanceFrom.Locked == "0") { 143 | stateMap.StateBalanceMap[keyBalanceFrom] = nil 144 | } 145 | //////////////////////////////// 146 | opData.OpAccept = 1 147 | return nil 148 | } 149 | 150 | //////////////////////////////// 151 | /*func (opMethodTransfer OpMethodTransfer) UnDo() (error) { 152 | // ... 153 | return nil 154 | }*/ 155 | 156 | // ... 157 | -------------------------------------------------------------------------------- /protowire/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package protowire; 3 | 4 | option go_package = ".;protowire"; 5 | 6 | import "rpc.proto"; 7 | 8 | message KaspadRequest { 9 | uint64 id = 101; 10 | oneof payload { 11 | GetCurrentNetworkRequestMessage getCurrentNetworkRequest = 1001; 12 | SubmitBlockRequestMessage submitBlockRequest = 1003; 13 | GetBlockTemplateRequestMessage getBlockTemplateRequest = 1005; 14 | NotifyBlockAddedRequestMessage notifyBlockAddedRequest = 1007; 15 | // BlockAddedNotificationMessage blockAddedNotification = 1009; 16 | GetPeerAddressesRequestMessage getPeerAddressesRequest = 1010; 17 | GetSinkRequestMessage GetSinkRequest = 1012; 18 | GetMempoolEntryRequestMessage getMempoolEntryRequest = 1014; 19 | GetConnectedPeerInfoRequestMessage getConnectedPeerInfoRequest = 1016; 20 | AddPeerRequestMessage addPeerRequest = 1018; 21 | SubmitTransactionRequestMessage submitTransactionRequest = 1020; 22 | NotifyVirtualChainChangedRequestMessage notifyVirtualChainChangedRequest = 1022; 23 | // VirtualChainChangedNotificationMessage virtualChainChangedNotification = 1024; 24 | GetBlockRequestMessage getBlockRequest = 1025; 25 | GetSubnetworkRequestMessage getSubnetworkRequest = 1027; 26 | GetVirtualChainFromBlockRequestMessage getVirtualChainFromBlockRequest = 1029; 27 | GetBlocksRequestMessage getBlocksRequest = 1031; 28 | GetBlockCountRequestMessage getBlockCountRequest = 1033; 29 | GetBlockDagInfoRequestMessage getBlockDagInfoRequest = 1035; 30 | ResolveFinalityConflictRequestMessage resolveFinalityConflictRequest = 1037; 31 | NotifyFinalityConflictRequestMessage notifyFinalityConflictRequest = 1039; 32 | // FinalityConflictNotificationMessage finalityConflictNotification = 1041; 33 | // FinalityConflictResolvedNotificationMessage finalityConflictResolvedNotification = 1042; 34 | GetMempoolEntriesRequestMessage getMempoolEntriesRequest = 1043; 35 | ShutdownRequestMessage shutdownRequest = 1045; 36 | GetHeadersRequestMessage getHeadersRequest = 1047; 37 | NotifyUtxosChangedRequestMessage notifyUtxosChangedRequest = 1049; 38 | // UtxosChangedNotificationMessage utxosChangedNotification = 1051; 39 | GetUtxosByAddressesRequestMessage getUtxosByAddressesRequest = 1052; 40 | GetSinkBlueScoreRequestMessage getSinkBlueScoreRequest = 1054; 41 | NotifySinkBlueScoreChangedRequestMessage notifySinkBlueScoreChangedRequest = 1056; 42 | // SinkBlueScoreChangedNotificationMessage sinkBlueScoreChangedNotification = 1058; 43 | BanRequestMessage banRequest = 1059; 44 | UnbanRequestMessage unbanRequest = 1061; 45 | GetInfoRequestMessage getInfoRequest = 1063; 46 | StopNotifyingUtxosChangedRequestMessage stopNotifyingUtxosChangedRequest = 1065; 47 | NotifyPruningPointUtxoSetOverrideRequestMessage notifyPruningPointUtxoSetOverrideRequest = 1067; 48 | // PruningPointUtxoSetOverrideNotificationMessage pruningPointUtxoSetOverrideNotification = 1069; 49 | StopNotifyingPruningPointUtxoSetOverrideRequestMessage stopNotifyingPruningPointUtxoSetOverrideRequest = 1070; 50 | EstimateNetworkHashesPerSecondRequestMessage estimateNetworkHashesPerSecondRequest = 1072; 51 | NotifyVirtualDaaScoreChangedRequestMessage notifyVirtualDaaScoreChangedRequest = 1074; 52 | // VirtualDaaScoreChangedNotificationMessage virtualDaaScoreChangedNotification = 1076; 53 | GetBalanceByAddressRequestMessage getBalanceByAddressRequest = 1077; 54 | GetBalancesByAddressesRequestMessage getBalancesByAddressesRequest = 1079; 55 | NotifyNewBlockTemplateRequestMessage notifyNewBlockTemplateRequest = 1081; 56 | // NewBlockTemplateNotificationMessage newBlockTemplateNotification = 1083; 57 | GetMempoolEntriesByAddressesRequestMessage getMempoolEntriesByAddressesRequest = 1084; 58 | GetCoinSupplyRequestMessage getCoinSupplyRequest = 1086; 59 | PingRequestMessage pingRequest = 1088; 60 | GetMetricsRequestMessage getMetricsRequest = 1090; 61 | GetServerInfoRequestMessage getServerInfoRequest = 1092; 62 | GetSyncStatusRequestMessage getSyncStatusRequest = 1094; 63 | GetDaaScoreTimestampEstimateRequestMessage getDaaScoreTimestampEstimateRequest = 1096; 64 | SubmitTransactionReplacementRequestMessage submitTransactionReplacementRequest = 1100; 65 | GetConnectionsRequestMessage getConnectionsRequest = 1102; 66 | GetSystemInfoRequestMessage getSystemInfoRequest = 1104; 67 | GetFeeEstimateRequestMessage getFeeEstimateRequest = 1106; 68 | GetFeeEstimateExperimentalRequestMessage getFeeEstimateExperimentalRequest = 1108; 69 | GetCurrentBlockColorRequestMessage getCurrentBlockColorRequest = 1110; 70 | GetUtxoReturnAddressRequestMessage GetUtxoReturnAddressRequest = 1112; 71 | } 72 | } 73 | 74 | message KaspadResponse { 75 | uint64 id = 101; 76 | oneof payload { 77 | GetCurrentNetworkResponseMessage getCurrentNetworkResponse = 1002; 78 | SubmitBlockResponseMessage submitBlockResponse = 1004; 79 | GetBlockTemplateResponseMessage getBlockTemplateResponse = 1006; 80 | NotifyBlockAddedResponseMessage notifyBlockAddedResponse = 1008; 81 | BlockAddedNotificationMessage blockAddedNotification = 1009; 82 | GetPeerAddressesResponseMessage getPeerAddressesResponse = 1011; 83 | GetSinkResponseMessage GetSinkResponse = 1013; 84 | GetMempoolEntryResponseMessage getMempoolEntryResponse = 1015; 85 | GetConnectedPeerInfoResponseMessage getConnectedPeerInfoResponse = 1017; 86 | AddPeerResponseMessage addPeerResponse = 1019; 87 | SubmitTransactionResponseMessage submitTransactionResponse = 1021; 88 | NotifyVirtualChainChangedResponseMessage notifyVirtualChainChangedResponse = 1023; 89 | VirtualChainChangedNotificationMessage virtualChainChangedNotification = 1024; 90 | GetBlockResponseMessage getBlockResponse = 1026; 91 | GetSubnetworkResponseMessage getSubnetworkResponse = 1028; 92 | GetVirtualChainFromBlockResponseMessage getVirtualChainFromBlockResponse = 1030; 93 | GetBlocksResponseMessage getBlocksResponse = 1032; 94 | GetBlockCountResponseMessage getBlockCountResponse = 1034; 95 | GetBlockDagInfoResponseMessage getBlockDagInfoResponse = 1036; 96 | ResolveFinalityConflictResponseMessage resolveFinalityConflictResponse = 1038; 97 | NotifyFinalityConflictResponseMessage notifyFinalityConflictResponse = 1040; 98 | FinalityConflictNotificationMessage finalityConflictNotification = 1041; 99 | FinalityConflictResolvedNotificationMessage finalityConflictResolvedNotification = 1042; 100 | GetMempoolEntriesResponseMessage getMempoolEntriesResponse = 1044; 101 | ShutdownResponseMessage shutdownResponse = 1046; 102 | GetHeadersResponseMessage getHeadersResponse = 1048; 103 | NotifyUtxosChangedResponseMessage notifyUtxosChangedResponse = 1050; 104 | UtxosChangedNotificationMessage utxosChangedNotification = 1051; 105 | GetUtxosByAddressesResponseMessage getUtxosByAddressesResponse = 1053; 106 | GetSinkBlueScoreResponseMessage getSinkBlueScoreResponse = 1055; 107 | NotifySinkBlueScoreChangedResponseMessage notifySinkBlueScoreChangedResponse = 1057; 108 | SinkBlueScoreChangedNotificationMessage sinkBlueScoreChangedNotification = 1058; 109 | BanResponseMessage banResponse = 1060; 110 | UnbanResponseMessage unbanResponse = 1062; 111 | GetInfoResponseMessage getInfoResponse = 1064; 112 | StopNotifyingUtxosChangedResponseMessage stopNotifyingUtxosChangedResponse = 1066; 113 | NotifyPruningPointUtxoSetOverrideResponseMessage notifyPruningPointUtxoSetOverrideResponse = 1068; 114 | PruningPointUtxoSetOverrideNotificationMessage pruningPointUtxoSetOverrideNotification = 1069; 115 | StopNotifyingPruningPointUtxoSetOverrideResponseMessage stopNotifyingPruningPointUtxoSetOverrideResponse = 1071; 116 | EstimateNetworkHashesPerSecondResponseMessage estimateNetworkHashesPerSecondResponse = 1073; 117 | NotifyVirtualDaaScoreChangedResponseMessage notifyVirtualDaaScoreChangedResponse = 1075; 118 | VirtualDaaScoreChangedNotificationMessage virtualDaaScoreChangedNotification = 1076; 119 | GetBalanceByAddressResponseMessage getBalanceByAddressResponse = 1078; 120 | GetBalancesByAddressesResponseMessage getBalancesByAddressesResponse = 1080; 121 | NotifyNewBlockTemplateResponseMessage notifyNewBlockTemplateResponse = 1082; 122 | NewBlockTemplateNotificationMessage newBlockTemplateNotification = 1083; 123 | GetMempoolEntriesByAddressesResponseMessage getMempoolEntriesByAddressesResponse = 1085; 124 | GetCoinSupplyResponseMessage getCoinSupplyResponse= 1087; 125 | PingResponseMessage pingResponse= 1089; 126 | GetMetricsResponseMessage getMetricsResponse= 1091; 127 | GetServerInfoResponseMessage getServerInfoResponse = 1093; 128 | GetSyncStatusResponseMessage getSyncStatusResponse = 1095; 129 | GetDaaScoreTimestampEstimateResponseMessage getDaaScoreTimestampEstimateResponse = 1097; 130 | SubmitTransactionReplacementResponseMessage submitTransactionReplacementResponse = 1101; 131 | GetConnectionsResponseMessage getConnectionsResponse= 1103; 132 | GetSystemInfoResponseMessage getSystemInfoResponse= 1105; 133 | GetFeeEstimateResponseMessage getFeeEstimateResponse = 1107; 134 | GetFeeEstimateExperimentalResponseMessage getFeeEstimateExperimentalResponse = 1109; 135 | GetCurrentBlockColorResponseMessage getCurrentBlockColorResponse = 1111; 136 | GetUtxoReturnAddressResponseMessage GetUtxoReturnAddressResponse = 1113; 137 | } 138 | } 139 | 140 | service RPC { 141 | rpc MessageStream (stream KaspadRequest) returns (stream KaspadResponse) {} 142 | } -------------------------------------------------------------------------------- /protowire/message_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | // versions: 3 | // - protoc-gen-go-grpc v1.5.1 4 | // - protoc v3.6.1 5 | // source: message.proto 6 | 7 | package protowire 8 | 9 | import ( 10 | context "context" 11 | grpc "google.golang.org/grpc" 12 | codes "google.golang.org/grpc/codes" 13 | status "google.golang.org/grpc/status" 14 | ) 15 | 16 | // This is a compile-time assertion to ensure that this generated file 17 | // is compatible with the grpc package it is being compiled against. 18 | // Requires gRPC-Go v1.64.0 or later. 19 | const _ = grpc.SupportPackageIsVersion9 20 | 21 | const ( 22 | RPC_MessageStream_FullMethodName = "/protowire.RPC/MessageStream" 23 | ) 24 | 25 | // RPCClient is the client API for RPC service. 26 | // 27 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 28 | type RPCClient interface { 29 | MessageStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[KaspadRequest, KaspadResponse], error) 30 | } 31 | 32 | type rPCClient struct { 33 | cc grpc.ClientConnInterface 34 | } 35 | 36 | func NewRPCClient(cc grpc.ClientConnInterface) RPCClient { 37 | return &rPCClient{cc} 38 | } 39 | 40 | func (c *rPCClient) MessageStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[KaspadRequest, KaspadResponse], error) { 41 | cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) 42 | stream, err := c.cc.NewStream(ctx, &RPC_ServiceDesc.Streams[0], RPC_MessageStream_FullMethodName, cOpts...) 43 | if err != nil { 44 | return nil, err 45 | } 46 | x := &grpc.GenericClientStream[KaspadRequest, KaspadResponse]{ClientStream: stream} 47 | return x, nil 48 | } 49 | 50 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 51 | type RPC_MessageStreamClient = grpc.BidiStreamingClient[KaspadRequest, KaspadResponse] 52 | 53 | // RPCServer is the server API for RPC service. 54 | // All implementations must embed UnimplementedRPCServer 55 | // for forward compatibility. 56 | type RPCServer interface { 57 | MessageStream(grpc.BidiStreamingServer[KaspadRequest, KaspadResponse]) error 58 | mustEmbedUnimplementedRPCServer() 59 | } 60 | 61 | // UnimplementedRPCServer must be embedded to have 62 | // forward compatible implementations. 63 | // 64 | // NOTE: this should be embedded by value instead of pointer to avoid a nil 65 | // pointer dereference when methods are called. 66 | type UnimplementedRPCServer struct{} 67 | 68 | func (UnimplementedRPCServer) MessageStream(grpc.BidiStreamingServer[KaspadRequest, KaspadResponse]) error { 69 | return status.Errorf(codes.Unimplemented, "method MessageStream not implemented") 70 | } 71 | func (UnimplementedRPCServer) mustEmbedUnimplementedRPCServer() {} 72 | func (UnimplementedRPCServer) testEmbeddedByValue() {} 73 | 74 | // UnsafeRPCServer may be embedded to opt out of forward compatibility for this service. 75 | // Use of this interface is not recommended, as added methods to RPCServer will 76 | // result in compilation errors. 77 | type UnsafeRPCServer interface { 78 | mustEmbedUnimplementedRPCServer() 79 | } 80 | 81 | func RegisterRPCServer(s grpc.ServiceRegistrar, srv RPCServer) { 82 | // If the following call pancis, it indicates UnimplementedRPCServer was 83 | // embedded by pointer and is nil. This will cause panics if an 84 | // unimplemented method is ever invoked, so we test this at initialization 85 | // time to prevent it from happening at runtime later due to I/O. 86 | if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { 87 | t.testEmbeddedByValue() 88 | } 89 | s.RegisterService(&RPC_ServiceDesc, srv) 90 | } 91 | 92 | func _RPC_MessageStream_Handler(srv interface{}, stream grpc.ServerStream) error { 93 | return srv.(RPCServer).MessageStream(&grpc.GenericServerStream[KaspadRequest, KaspadResponse]{ServerStream: stream}) 94 | } 95 | 96 | // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. 97 | type RPC_MessageStreamServer = grpc.BidiStreamingServer[KaspadRequest, KaspadResponse] 98 | 99 | // RPC_ServiceDesc is the grpc.ServiceDesc for RPC service. 100 | // It's only intended for direct use with grpc.RegisterService, 101 | // and not to be introspected or modified (even as a copy) 102 | var RPC_ServiceDesc = grpc.ServiceDesc{ 103 | ServiceName: "protowire.RPC", 104 | HandlerType: (*RPCServer)(nil), 105 | Methods: []grpc.MethodDesc{}, 106 | Streams: []grpc.StreamDesc{ 107 | { 108 | StreamName: "MessageStream", 109 | Handler: _RPC_MessageStream_Handler, 110 | ServerStreams: true, 111 | ClientStreams: true, 112 | }, 113 | }, 114 | Metadata: "message.proto", 115 | } 116 | -------------------------------------------------------------------------------- /protowire/rpc.proto: -------------------------------------------------------------------------------- 1 | // RPC-related types. Request messages, response messages, and dependant types. 2 | // 3 | // Clients are expected to build RequestMessages and wrap them in KaspadMessage. (see messages.proto) 4 | // 5 | // Having received a RequestMessage, (wrapped in a KaspadMessage) the RPC server will respond with a 6 | // ResponseMessage (likewise wrapped in a KaspadMessage) respective to the original RequestMessage. 7 | // 8 | // 9 | syntax = "proto3"; 10 | package protowire; 11 | 12 | option go_package = ".;protowire"; 13 | 14 | // RPCError represents a generic non-internal error. 15 | // 16 | // Receivers of any ResponseMessage are expected to check whether its error field is not null. 17 | message RPCError{ 18 | string message = 1; 19 | } 20 | 21 | message RpcBlock { 22 | RpcBlockHeader header = 1; 23 | repeated RpcTransaction transactions = 2; 24 | RpcBlockVerboseData verboseData = 3; 25 | } 26 | 27 | message RpcBlockHeader { 28 | uint32 version = 1; 29 | //repeated RpcBlockLevelParents parents = 12; 30 | //string hashMerkleRoot = 3; 31 | string acceptedIdMerkleRoot = 4; 32 | //string utxoCommitment = 5; 33 | int64 timestamp = 6; 34 | //uint32 bits = 7; 35 | //uint64 nonce = 8; 36 | uint64 daaScore = 9; 37 | //string blueWork = 10; 38 | //string pruningPoint = 14; 39 | uint64 blueScore = 13; 40 | } 41 | 42 | message RpcBlockLevelParents { 43 | repeated string parentHashes = 1; 44 | } 45 | 46 | message RpcBlockVerboseData{ 47 | string hash = 1; 48 | //double difficulty = 11; 49 | string selectedParentHash = 13; 50 | //repeated string transactionIds = 14; 51 | //bool isHeaderOnly = 15; 52 | //uint64 blueScore = 16; 53 | //repeated string childrenHashes = 17; 54 | //repeated string mergeSetBluesHashes = 18; 55 | //repeated string mergeSetRedsHashes = 19; 56 | //bool isChainBlock = 20; 57 | } 58 | 59 | message RpcTransaction { 60 | uint32 version = 1; 61 | repeated RpcTransactionInput inputs = 2; 62 | repeated RpcTransactionOutput outputs = 3; 63 | uint64 lockTime = 4; 64 | string subnetworkId = 5; 65 | uint64 gas = 6; 66 | string payload = 8; 67 | RpcTransactionVerboseData verboseData = 9; 68 | uint64 mass = 10; 69 | } 70 | 71 | message RpcTransactionInput { 72 | RpcOutpoint previousOutpoint = 1; 73 | string signatureScript = 2; 74 | uint64 sequence = 3; 75 | uint32 sigOpCount = 5; 76 | RpcTransactionInputVerboseData verboseData = 4; 77 | } 78 | 79 | message RpcScriptPublicKey { 80 | uint32 version = 1; 81 | string scriptPublicKey = 2; 82 | } 83 | 84 | message RpcTransactionOutput { 85 | uint64 amount = 1; 86 | RpcScriptPublicKey scriptPublicKey = 2; 87 | RpcTransactionOutputVerboseData verboseData = 3; 88 | } 89 | 90 | message RpcOutpoint { 91 | string transactionId = 1; 92 | uint32 index = 2; 93 | } 94 | 95 | message RpcUtxoEntry { 96 | uint64 amount = 1; 97 | RpcScriptPublicKey scriptPublicKey = 2; 98 | uint64 blockDaaScore = 3; 99 | bool isCoinbase = 4; 100 | } 101 | 102 | message RpcTransactionVerboseData{ 103 | string transactionId = 1; 104 | string hash = 2; 105 | uint64 computeMass = 4; 106 | string blockHash = 12; 107 | uint64 blockTime = 14; 108 | } 109 | 110 | message RpcTransactionInputVerboseData{ 111 | } 112 | 113 | message RpcTransactionOutputVerboseData{ 114 | string scriptPublicKeyType = 5; 115 | string scriptPublicKeyAddress = 6; 116 | } 117 | 118 | enum RpcNotifyCommand { 119 | NOTIFY_START = 0; 120 | NOTIFY_STOP = 1; 121 | } 122 | 123 | // GetCurrentNetworkRequestMessage requests the network kaspad is currently running against. 124 | // 125 | // Possible networks are: Mainnet, Testnet, Simnet, Devnet 126 | message GetCurrentNetworkRequestMessage{ 127 | } 128 | 129 | message GetCurrentNetworkResponseMessage{ 130 | string currentNetwork = 1; 131 | RPCError error = 1000; 132 | } 133 | 134 | // SubmitBlockRequestMessage requests to submit a block into the DAG. 135 | // Blocks are generally expected to have been generated using the getBlockTemplate call. 136 | // 137 | // See: GetBlockTemplateRequestMessage 138 | message SubmitBlockRequestMessage{ 139 | RpcBlock block = 2; 140 | bool allowNonDAABlocks = 3; 141 | } 142 | 143 | message SubmitBlockResponseMessage{ 144 | enum RejectReason { 145 | NONE = 0; 146 | BLOCK_INVALID = 1; 147 | IS_IN_IBD = 2; 148 | } 149 | RejectReason rejectReason = 1; 150 | RPCError error = 1000; 151 | } 152 | 153 | // GetBlockTemplateRequestMessage requests a current block template. 154 | // Callers are expected to solve the block template and submit it using the submitBlock call 155 | // 156 | // See: SubmitBlockRequestMessage 157 | message GetBlockTemplateRequestMessage{ 158 | // Which kaspa address should the coinbase block reward transaction pay into 159 | string payAddress = 1; 160 | string extraData = 2; 161 | } 162 | 163 | message GetBlockTemplateResponseMessage{ 164 | RpcBlock block = 3; 165 | 166 | // Whether kaspad thinks that it's synced. 167 | // Callers are discouraged (but not forbidden) from solving blocks when kaspad is not synced. 168 | // That is because when kaspad isn't in sync with the rest of the network there's a high 169 | // chance the block will never be accepted, thus the solving effort would have been wasted. 170 | bool isSynced = 2; 171 | 172 | RPCError error = 1000; 173 | } 174 | 175 | // NotifyBlockAddedRequestMessage registers this connection for blockAdded notifications. 176 | // 177 | // See: BlockAddedNotificationMessage 178 | message NotifyBlockAddedRequestMessage{ 179 | RpcNotifyCommand command = 101; 180 | } 181 | 182 | message NotifyBlockAddedResponseMessage{ 183 | RPCError error = 1000; 184 | } 185 | 186 | // BlockAddedNotificationMessage is sent whenever a blocks has been added (NOT accepted) 187 | // into the DAG. 188 | // 189 | // See: NotifyBlockAddedRequestMessage 190 | message BlockAddedNotificationMessage{ 191 | RpcBlock block = 3; 192 | } 193 | 194 | // GetPeerAddressesRequestMessage requests the list of known kaspad addresses in the 195 | // current network. (mainnet, testnet, etc.) 196 | message GetPeerAddressesRequestMessage{ 197 | } 198 | 199 | message GetPeerAddressesResponseMessage{ 200 | repeated GetPeerAddressesKnownAddressMessage addresses = 1; 201 | repeated GetPeerAddressesKnownAddressMessage bannedAddresses = 2; 202 | RPCError error = 1000; 203 | } 204 | 205 | message GetPeerAddressesKnownAddressMessage { 206 | string Addr = 1; 207 | } 208 | 209 | // GetSinkRequestMessage requests the hash of the current virtual's 210 | // selected parent. 211 | message GetSinkRequestMessage{ 212 | } 213 | 214 | message GetSinkResponseMessage{ 215 | string sink = 1; 216 | RPCError error = 1000; 217 | } 218 | 219 | // GetMempoolEntryRequestMessage requests information about a specific transaction 220 | // in the mempool. 221 | message GetMempoolEntryRequestMessage{ 222 | // The transaction's TransactionID. 223 | string txId = 1; 224 | bool includeOrphanPool = 2; 225 | bool filterTransactionPool = 3; 226 | } 227 | 228 | message GetMempoolEntryResponseMessage{ 229 | RpcMempoolEntry entry = 1; 230 | 231 | RPCError error = 1000; 232 | } 233 | 234 | // GetMempoolEntriesRequestMessage requests information about all the transactions 235 | // currently in the mempool. 236 | message GetMempoolEntriesRequestMessage{ 237 | bool includeOrphanPool = 1; 238 | bool filterTransactionPool = 2; 239 | } 240 | 241 | message GetMempoolEntriesResponseMessage{ 242 | repeated RpcMempoolEntry entries = 1; 243 | 244 | RPCError error = 1000; 245 | } 246 | 247 | message RpcMempoolEntry{ 248 | uint64 fee = 1; 249 | RpcTransaction transaction = 3; 250 | bool isOrphan = 4; 251 | } 252 | 253 | // GetConnectedPeerInfoRequestMessage requests information about all the p2p peers 254 | // currently connected to this kaspad. 255 | message GetConnectedPeerInfoRequestMessage{ 256 | } 257 | 258 | message GetConnectedPeerInfoResponseMessage{ 259 | repeated GetConnectedPeerInfoMessage infos = 1; 260 | RPCError error = 1000; 261 | } 262 | 263 | message GetConnectedPeerInfoMessage{ 264 | string id = 1; 265 | string address = 2; 266 | 267 | // How long did the last ping/pong exchange take 268 | int64 lastPingDuration = 3; 269 | 270 | // Whether this kaspad initiated the connection 271 | bool isOutbound = 6; 272 | int64 timeOffset = 7; 273 | string userAgent = 8; 274 | 275 | // The protocol version that this peer claims to support 276 | uint32 advertisedProtocolVersion = 9; 277 | 278 | // The timestamp of when this peer connected to this kaspad 279 | int64 timeConnected = 10; 280 | 281 | // Whether this peer is the IBD peer (if IBD is running) 282 | bool isIbdPeer = 11; 283 | } 284 | 285 | // AddPeerRequestMessage adds a peer to kaspad's outgoing connection list. 286 | // This will, in most cases, result in kaspad connecting to said peer. 287 | message AddPeerRequestMessage{ 288 | string address = 1; 289 | 290 | // Whether to keep attempting to connect to this peer after disconnection 291 | bool isPermanent = 2; 292 | } 293 | 294 | message AddPeerResponseMessage{ 295 | RPCError error = 1000; 296 | } 297 | 298 | // SubmitTransactionRequestMessage submits a transaction to the mempool 299 | message SubmitTransactionRequestMessage{ 300 | RpcTransaction transaction = 1; 301 | bool allowOrphan = 2; 302 | } 303 | 304 | message SubmitTransactionResponseMessage{ 305 | // The transaction ID of the submitted transaction 306 | string transactionId = 1; 307 | 308 | RPCError error = 1000; 309 | } 310 | 311 | // SubmitTransactionReplacementRequestMessage submits a transaction to the mempool, applying a mandatory Replace by Fee policy 312 | message SubmitTransactionReplacementRequestMessage{ 313 | RpcTransaction transaction = 1; 314 | } 315 | 316 | message SubmitTransactionReplacementResponseMessage{ 317 | // The transaction ID of the submitted transaction 318 | string transactionId = 1; 319 | 320 | // The previous transaction replaced in the mempool by the newly submitted one 321 | RpcTransaction replacedTransaction = 2; 322 | 323 | RPCError error = 1000; 324 | } 325 | 326 | // NotifyVirtualChainChangedRequestMessage registers this connection for virtualChainChanged notifications. 327 | // 328 | // See: VirtualChainChangedNotificationMessage 329 | message NotifyVirtualChainChangedRequestMessage{ 330 | bool includeAcceptedTransactionIds = 1; 331 | RpcNotifyCommand command = 101; 332 | } 333 | 334 | message NotifyVirtualChainChangedResponseMessage{ 335 | RPCError error = 1000; 336 | } 337 | 338 | // VirtualChainChangedNotificationMessage is sent whenever the DAG's selected parent 339 | // chain had changed. 340 | // 341 | // See: NotifyVirtualChainChangedRequestMessage 342 | message VirtualChainChangedNotificationMessage{ 343 | // The chain blocks that were removed, in high-to-low order 344 | repeated string removedChainBlockHashes = 1; 345 | 346 | // The chain blocks that were added, in low-to-high order 347 | repeated string addedChainBlockHashes = 3; 348 | 349 | // Will be filled only if `includeAcceptedTransactionIds = true` in the notify request. 350 | repeated RpcAcceptedTransactionIds acceptedTransactionIds = 2; 351 | } 352 | 353 | // GetBlockRequestMessage requests information about a specific block 354 | message GetBlockRequestMessage{ 355 | // The hash of the requested block 356 | string hash = 1; 357 | 358 | // Whether to include transaction data in the response 359 | bool includeTransactions = 3; 360 | } 361 | 362 | message GetBlockResponseMessage{ 363 | RpcBlock block = 3; 364 | RPCError error = 1000; 365 | } 366 | 367 | // GetSubnetworkRequestMessage requests information about a specific subnetwork 368 | // 369 | // Currently unimplemented 370 | message GetSubnetworkRequestMessage{ 371 | string subnetworkId = 1; 372 | } 373 | 374 | message GetSubnetworkResponseMessage{ 375 | uint64 gasLimit = 1; 376 | RPCError error = 1000; 377 | } 378 | 379 | /// GetVirtualChainFromBlockRequestMessage requests the virtual selected 380 | /// parent chain from some startHash to this kaspad's current virtual 381 | /// Note: 382 | /// this call batches the response to: 383 | /// a. the network's `mergeset size limit * 10` amount of added chain blocks, if `includeAcceptedTransactionIds = false` 384 | /// b. or `mergeset size limit * 10` amount of merged blocks, if `includeAcceptedTransactionIds = true` 385 | /// c. it does not batch the removed chain blocks, only the added ones. 386 | message GetVirtualChainFromBlockRequestMessage{ 387 | string startHash = 1; 388 | bool includeAcceptedTransactionIds = 2; 389 | } 390 | 391 | message RpcAcceptedTransactionIds{ 392 | string acceptingBlockHash = 1; 393 | repeated string acceptedTransactionIds = 2; 394 | } 395 | 396 | message GetVirtualChainFromBlockResponseMessage{ 397 | // The chain blocks that were removed, in high-to-low order 398 | repeated string removedChainBlockHashes = 1; 399 | 400 | // The chain blocks that were added, in low-to-high order 401 | repeated string addedChainBlockHashes = 3; 402 | 403 | // The transactions accepted by each block in addedChainBlockHashes. 404 | // Will be filled only if `includeAcceptedTransactionIds = true` in the request. 405 | repeated RpcAcceptedTransactionIds acceptedTransactionIds = 2; 406 | 407 | RPCError error = 1000; 408 | } 409 | 410 | // GetBlocksRequestMessage requests blocks between a certain block lowHash up to this 411 | // kaspad's current virtual. 412 | message GetBlocksRequestMessage{ 413 | string lowHash = 1; 414 | bool includeBlocks = 2; 415 | bool includeTransactions = 3; 416 | } 417 | 418 | message GetBlocksResponseMessage{ 419 | repeated string blockHashes = 4; 420 | repeated RpcBlock blocks = 3; 421 | RPCError error = 1000; 422 | } 423 | 424 | // GetBlockCountRequestMessage requests the current number of blocks in this kaspad. 425 | // Note that this number may decrease as pruning occurs. 426 | message GetBlockCountRequestMessage{ 427 | } 428 | 429 | message GetBlockCountResponseMessage{ 430 | uint64 blockCount = 1; 431 | uint64 headerCount = 2; 432 | RPCError error = 1000; 433 | } 434 | 435 | // GetBlockDagInfoRequestMessage requests general information about the current state 436 | // of this kaspad's DAG. 437 | message GetBlockDagInfoRequestMessage{ 438 | } 439 | 440 | message GetBlockDagInfoResponseMessage{ 441 | string networkName = 1; 442 | uint64 blockCount = 2; 443 | uint64 headerCount = 3; 444 | repeated string tipHashes = 4; 445 | double difficulty = 5; 446 | int64 pastMedianTime = 6; 447 | repeated string virtualParentHashes = 7; 448 | string pruningPointHash = 8; 449 | uint64 virtualDaaScore = 9; 450 | string sink = 10; 451 | RPCError error = 1000; 452 | } 453 | 454 | message ResolveFinalityConflictRequestMessage{ 455 | string finalityBlockHash = 1; 456 | } 457 | 458 | message ResolveFinalityConflictResponseMessage{ 459 | RPCError error = 1000; 460 | } 461 | 462 | message NotifyFinalityConflictRequestMessage{ 463 | RpcNotifyCommand command = 101; 464 | } 465 | 466 | message NotifyFinalityConflictResponseMessage{ 467 | RPCError error = 1000; 468 | } 469 | 470 | message FinalityConflictNotificationMessage{ 471 | string violatingBlockHash = 1; 472 | } 473 | 474 | message FinalityConflictResolvedNotificationMessage{ 475 | string finalityBlockHash = 1; 476 | } 477 | 478 | // ShutdownRequestMessage shuts down this kaspad. 479 | message ShutdownRequestMessage{ 480 | } 481 | 482 | message ShutdownResponseMessage{ 483 | RPCError error = 1000; 484 | } 485 | 486 | // GetHeadersRequestMessage requests headers between the given startHash and the 487 | // current virtual, up to the given limit. 488 | message GetHeadersRequestMessage{ 489 | string startHash = 1; 490 | uint64 limit = 2; 491 | bool isAscending = 3; 492 | } 493 | 494 | message GetHeadersResponseMessage{ 495 | repeated string headers = 1; 496 | RPCError error = 1000; 497 | } 498 | 499 | // NotifyUtxosChangedRequestMessage registers this connection for utxoChanged notifications 500 | // for the given addresses. 501 | // 502 | // This call is only available when this kaspad was started with `--utxoindex` 503 | // 504 | // See: UtxosChangedNotificationMessage 505 | message NotifyUtxosChangedRequestMessage { 506 | // UTXOs addresses to start/stop getting notified about 507 | // Leave empty to start/stop all updates 508 | repeated string addresses = 1; 509 | RpcNotifyCommand command = 101; 510 | } 511 | 512 | message NotifyUtxosChangedResponseMessage { 513 | RPCError error = 1000; 514 | } 515 | 516 | // UtxosChangedNotificationMessage is sent whenever the UTXO index had been updated. 517 | // 518 | // See: NotifyUtxosChangedRequestMessage 519 | message UtxosChangedNotificationMessage { 520 | repeated RpcUtxosByAddressesEntry added = 1; 521 | repeated RpcUtxosByAddressesEntry removed = 2; 522 | } 523 | 524 | message RpcUtxosByAddressesEntry { 525 | string address = 1; 526 | RpcOutpoint outpoint = 2; 527 | RpcUtxoEntry utxoEntry = 3; 528 | } 529 | 530 | // StopNotifyingUtxosChangedRequestMessage unregisters this connection for utxoChanged notifications 531 | // for the given addresses. 532 | // 533 | // This call is only available when this kaspad was started with `--utxoindex` 534 | // 535 | // See: UtxosChangedNotificationMessage 536 | // 537 | // This message only exists for backward compatibility reason with kaspad and is deprecated. 538 | // Use instead UtxosChangedNotificationMessage with command = NOTIFY_STOP. 539 | message StopNotifyingUtxosChangedRequestMessage { 540 | repeated string addresses = 1; 541 | } 542 | 543 | message StopNotifyingUtxosChangedResponseMessage { 544 | RPCError error = 1000; 545 | } 546 | 547 | // GetUtxosByAddressesRequestMessage requests all current UTXOs for the given kaspad addresses 548 | // 549 | // This call is only available when this kaspad was started with `--utxoindex` 550 | message GetUtxosByAddressesRequestMessage { 551 | repeated string addresses = 1; 552 | } 553 | 554 | message GetUtxosByAddressesResponseMessage { 555 | repeated RpcUtxosByAddressesEntry entries = 1; 556 | 557 | RPCError error = 1000; 558 | } 559 | 560 | // GetBalanceByAddressRequest returns the total balance in unspent transactions towards a given address 561 | // 562 | // This call is only available when this kaspad was started with `--utxoindex` 563 | message GetBalanceByAddressRequestMessage { 564 | string address = 1; 565 | } 566 | 567 | message GetBalanceByAddressResponseMessage { 568 | uint64 balance = 1; 569 | 570 | RPCError error = 1000; 571 | } 572 | 573 | message GetBalancesByAddressesRequestMessage { 574 | repeated string addresses = 1; 575 | } 576 | 577 | message RpcBalancesByAddressesEntry{ 578 | string address = 1; 579 | uint64 balance = 2; 580 | 581 | RPCError error = 1000; 582 | } 583 | 584 | message GetBalancesByAddressesResponseMessage { 585 | repeated RpcBalancesByAddressesEntry entries = 1; 586 | 587 | RPCError error = 1000; 588 | } 589 | 590 | // GetSinkBlueScoreRequestMessage requests the blue score of the current selected parent 591 | // of the virtual block. 592 | message GetSinkBlueScoreRequestMessage { 593 | } 594 | 595 | message GetSinkBlueScoreResponseMessage { 596 | uint64 blueScore = 1; 597 | 598 | RPCError error = 1000; 599 | } 600 | 601 | // NotifySinkBlueScoreChangedRequestMessage registers this connection for 602 | // sinkBlueScoreChanged notifications. 603 | // 604 | // See: SinkBlueScoreChangedNotificationMessage 605 | message NotifySinkBlueScoreChangedRequestMessage { 606 | RpcNotifyCommand command = 101; 607 | } 608 | 609 | message NotifySinkBlueScoreChangedResponseMessage { 610 | RPCError error = 1000; 611 | } 612 | 613 | // SinkBlueScoreChangedNotificationMessage is sent whenever the blue score 614 | // of the virtual's selected parent changes. 615 | // 616 | // See NotifySinkBlueScoreChangedRequestMessage 617 | message SinkBlueScoreChangedNotificationMessage { 618 | uint64 sinkBlueScore = 1; 619 | } 620 | 621 | // NotifyVirtualDaaScoreChangedRequestMessage registers this connection for 622 | // virtualDaaScoreChanged notifications. 623 | // 624 | // See: VirtualDaaScoreChangedNotificationMessage 625 | message NotifyVirtualDaaScoreChangedRequestMessage { 626 | RpcNotifyCommand command = 101; 627 | } 628 | 629 | message NotifyVirtualDaaScoreChangedResponseMessage { 630 | RPCError error = 1000; 631 | } 632 | 633 | // VirtualDaaScoreChangedNotificationMessage is sent whenever the DAA score 634 | // of the virtual changes. 635 | // 636 | // See NotifyVirtualDaaScoreChangedRequestMessage 637 | message VirtualDaaScoreChangedNotificationMessage { 638 | uint64 virtualDaaScore = 1; 639 | } 640 | 641 | // NotifyPruningPointUtxoSetOverrideRequestMessage registers this connection for 642 | // pruning point UTXO set override notifications. 643 | // 644 | // This call is only available when this kaspad was started with `--utxoindex` 645 | // 646 | // See: NotifyPruningPointUtxoSetOverrideResponseMessage 647 | message NotifyPruningPointUtxoSetOverrideRequestMessage { 648 | RpcNotifyCommand command = 101; 649 | } 650 | 651 | 652 | message NotifyPruningPointUtxoSetOverrideResponseMessage { 653 | RPCError error = 1000; 654 | } 655 | 656 | // PruningPointUtxoSetOverrideNotificationMessage is sent whenever the UTXO index 657 | // resets due to pruning point change via IBD. 658 | // 659 | // See NotifyPruningPointUtxoSetOverrideRequestMessage 660 | message PruningPointUtxoSetOverrideNotificationMessage { 661 | } 662 | 663 | // StopNotifyingPruningPointUtxoSetOverrideRequestMessage unregisters this connection for 664 | // pruning point UTXO set override notifications. 665 | // 666 | // This call is only available when this kaspad was started with `--utxoindex` 667 | // 668 | // See: PruningPointUtxoSetOverrideNotificationMessage 669 | // 670 | // This message only exists for backward compatibility reason with kaspad and is deprecated. 671 | // Use instead NotifyPruningPointUtxoSetOverrideRequestMessage with command = NOTIFY_STOP. 672 | message StopNotifyingPruningPointUtxoSetOverrideRequestMessage { 673 | } 674 | 675 | message StopNotifyingPruningPointUtxoSetOverrideResponseMessage { 676 | RPCError error = 1000; 677 | } 678 | 679 | // BanRequestMessage bans the given ip. 680 | message BanRequestMessage{ 681 | string ip = 1; 682 | } 683 | 684 | message BanResponseMessage{ 685 | RPCError error = 1000; 686 | } 687 | 688 | // UnbanRequestMessage unbans the given ip. 689 | message UnbanRequestMessage{ 690 | string ip = 1; 691 | } 692 | 693 | message UnbanResponseMessage{ 694 | RPCError error = 1000; 695 | } 696 | 697 | // GetInfoRequestMessage returns info about the node. 698 | message GetInfoRequestMessage{ 699 | } 700 | 701 | message GetInfoResponseMessage{ 702 | string p2pId = 1; 703 | uint64 mempoolSize = 2; 704 | string serverVersion = 3; 705 | bool isUtxoIndexed = 4; 706 | bool isSynced = 5; 707 | bool hasNotifyCommand = 11; 708 | bool hasMessageId = 12; 709 | RPCError error = 1000; 710 | } 711 | 712 | message EstimateNetworkHashesPerSecondRequestMessage{ 713 | uint32 windowSize = 1; 714 | string startHash = 2; 715 | } 716 | 717 | message EstimateNetworkHashesPerSecondResponseMessage{ 718 | uint64 networkHashesPerSecond = 1; 719 | RPCError error = 1000; 720 | } 721 | 722 | // NotifyNewBlockTemplateRequestMessage registers this connection for 723 | // NewBlockTemplate notifications. 724 | // 725 | // See: NewBlockTemplateNotificationMessage 726 | message NotifyNewBlockTemplateRequestMessage { 727 | RpcNotifyCommand command = 101; 728 | } 729 | 730 | message NotifyNewBlockTemplateResponseMessage { 731 | RPCError error = 1000; 732 | } 733 | 734 | // NewBlockTemplateNotificationMessage is sent whenever a new updated block template is 735 | // available for miners. 736 | // 737 | // See NotifyNewBlockTemplateRequestMessage 738 | message NewBlockTemplateNotificationMessage { 739 | } 740 | 741 | message RpcMempoolEntryByAddress{ 742 | string address = 1; 743 | repeated RpcMempoolEntry sending = 2; 744 | repeated RpcMempoolEntry receiving = 3; 745 | } 746 | 747 | message GetMempoolEntriesByAddressesRequestMessage{ 748 | repeated string addresses = 1; 749 | bool includeOrphanPool = 2; 750 | bool filterTransactionPool = 3; 751 | } 752 | 753 | message GetMempoolEntriesByAddressesResponseMessage{ 754 | repeated RpcMempoolEntryByAddress entries = 1; 755 | 756 | RPCError error = 1000; 757 | } 758 | 759 | message GetCoinSupplyRequestMessage{ 760 | } 761 | 762 | message GetCoinSupplyResponseMessage{ 763 | uint64 maxSompi = 1; // note: this is a hard coded maxSupply, actual maxSupply is expected to deviate by upto -5%, but cannot be measured exactly. 764 | uint64 circulatingSompi = 2; 765 | 766 | RPCError error = 1000; 767 | } 768 | 769 | message PingRequestMessage{ 770 | } 771 | 772 | message PingResponseMessage{ 773 | RPCError error = 1000; 774 | } 775 | 776 | message ProcessMetrics{ 777 | uint64 residentSetSize = 1; 778 | uint64 virtualMemorySize = 2; 779 | uint32 coreNum = 3; 780 | float cpuUsage = 4; 781 | uint32 fdNum = 5; 782 | uint64 diskIoReadBytes = 6; 783 | uint64 diskIoWriteBytes = 7; 784 | float diskIoReadPerSec = 8; 785 | float diskIoWritePerSec = 9; 786 | } 787 | 788 | message ConnectionMetrics { 789 | uint32 borshLiveConnections = 31; 790 | uint64 borshConnectionAttempts = 32; 791 | uint64 borshHandshakeFailures = 33; 792 | 793 | uint32 jsonLiveConnections = 41; 794 | uint64 jsonConnectionAttempts = 42; 795 | uint64 jsonHandshakeFailures = 43; 796 | 797 | uint32 activePeers = 51; 798 | } 799 | 800 | message BandwidthMetrics { 801 | uint64 borshBytesTx = 61; 802 | uint64 borshBytesRx = 62; 803 | uint64 jsonBytesTx = 63; 804 | uint64 jsonBytesRx = 64; 805 | uint64 grpcP2pBytesTx = 65; 806 | uint64 grpcP2pBytesRx = 66; 807 | uint64 grpcUserBytesTx = 67; 808 | uint64 grpcUserBytesRx = 68; 809 | } 810 | 811 | message ConsensusMetrics{ 812 | uint64 blocksSubmitted = 1; 813 | uint64 headerCounts = 2; 814 | uint64 depCounts = 3; 815 | uint64 bodyCounts = 4; 816 | uint64 txsCounts = 5; 817 | uint64 chainBlockCounts = 6; 818 | uint64 massCounts = 7; 819 | 820 | uint64 blockCount = 11; 821 | uint64 headerCount = 12; 822 | uint64 mempoolSize = 13; 823 | uint32 tipHashesCount = 14; 824 | double difficulty = 15; 825 | uint64 pastMedianTime = 16; 826 | uint32 virtualParentHashesCount = 17; 827 | uint64 virtualDaaScore = 18; 828 | } 829 | 830 | message StorageMetrics{ 831 | uint64 storageSizeBytes = 1; 832 | } 833 | 834 | message GetConnectionsRequestMessage{ 835 | bool includeProfileData = 1; 836 | } 837 | 838 | message ConnectionsProfileData { 839 | double cpuUsage = 1; 840 | uint64 memoryUsage = 2; 841 | } 842 | 843 | message GetConnectionsResponseMessage{ 844 | uint32 clients = 1; 845 | uint32 peers = 2; 846 | ConnectionsProfileData profileData = 3; 847 | RPCError error = 1000; 848 | } 849 | 850 | message GetSystemInfoRequestMessage{ 851 | } 852 | 853 | message GetSystemInfoResponseMessage{ 854 | string version = 1; 855 | string systemId = 2; 856 | string gitHash = 3; 857 | uint32 coreNum = 4; 858 | uint64 totalMemory = 5; 859 | uint32 fdLimit = 6; 860 | uint32 proxySocketLimitPerCpuCore = 7; 861 | RPCError error = 1000; 862 | } 863 | 864 | message GetMetricsRequestMessage{ 865 | bool processMetrics = 1; 866 | bool connectionMetrics = 2; 867 | bool bandwidthMetrics = 3; 868 | bool consensusMetrics = 4; 869 | bool storageMetrics = 5; 870 | bool customMetrics = 6; 871 | } 872 | 873 | message GetMetricsResponseMessage{ 874 | uint64 serverTime = 1; 875 | ProcessMetrics processMetrics = 11; 876 | ConnectionMetrics connectionMetrics = 12; 877 | BandwidthMetrics bandwidthMetrics = 13; 878 | ConsensusMetrics consensusMetrics = 14; 879 | StorageMetrics storageMetrics = 15; 880 | RPCError error = 1000; 881 | } 882 | 883 | message GetServerInfoRequestMessage{ 884 | } 885 | 886 | message GetServerInfoResponseMessage{ 887 | uint32 rpcApiVersion = 1; 888 | uint32 rpcApiRevision = 2; 889 | string serverVersion = 3; 890 | string networkId = 4; 891 | bool hasUtxoIndex = 5; 892 | bool isSynced = 6; 893 | uint64 virtualDaaScore = 7; 894 | RPCError error = 1000; 895 | } 896 | 897 | message GetSyncStatusRequestMessage{ 898 | } 899 | 900 | message GetSyncStatusResponseMessage{ 901 | bool isSynced = 1; 902 | RPCError error = 1000; 903 | } 904 | 905 | message GetDaaScoreTimestampEstimateRequestMessage { 906 | repeated uint64 daaScores = 1; 907 | } 908 | 909 | message GetDaaScoreTimestampEstimateResponseMessage { 910 | repeated uint64 timestamps = 1; 911 | RPCError error = 1000; 912 | } 913 | 914 | message RpcFeerateBucket { 915 | // Fee/mass of a transaction in `sompi/gram` units 916 | double feerate = 1; 917 | double estimatedSeconds = 2; 918 | } 919 | 920 | // Data required for making fee estimates. 921 | // 922 | // Feerate values represent fee/mass of a transaction in `sompi/gram` units. 923 | // Given a feerate value recommendation, calculate the required fee by 924 | // taking the transaction mass and multiplying it by feerate: `fee = feerate * mass(tx)` 925 | message RpcFeeEstimate { 926 | // Top-priority feerate bucket. Provides an estimation of the feerate required for sub-second DAG inclusion. 927 | RpcFeerateBucket priorityBucket = 1; 928 | 929 | // A vector of *normal* priority feerate values. The first value of this vector is guaranteed to exist and 930 | // provide an estimation for sub-*minute* DAG inclusion. All other values will have shorter estimation 931 | // times than all `lowBucket` values. Therefor by chaining `[priority] | normal | low` and interpolating 932 | // between them, one can compose a complete feerate function on the client side. The API makes an effort 933 | // to sample enough "interesting" points on the feerate-to-time curve, so that the interpolation is meaningful. 934 | repeated RpcFeerateBucket normalBuckets = 2; 935 | 936 | // A vector of *low* priority feerate values. The first value of this vector is guaranteed to 937 | // exist and provide an estimation for sub-*hour* DAG inclusion. 938 | repeated RpcFeerateBucket lowBuckets = 3; 939 | } 940 | 941 | message RpcFeeEstimateVerboseExperimentalData { 942 | uint64 mempoolReadyTransactionsCount = 1; 943 | uint64 mempoolReadyTransactionsTotalMass = 2; 944 | uint64 networkMassPerSecond = 3; 945 | 946 | double nextBlockTemplateFeerateMin = 11; 947 | double nextBlockTemplateFeerateMedian = 12; 948 | double nextBlockTemplateFeerateMax = 13; 949 | } 950 | 951 | message GetFeeEstimateRequestMessage { 952 | } 953 | 954 | message GetFeeEstimateResponseMessage { 955 | RpcFeeEstimate estimate = 1; 956 | RPCError error = 1000; 957 | } 958 | 959 | message GetFeeEstimateExperimentalRequestMessage { 960 | bool verbose = 1; 961 | } 962 | 963 | message GetFeeEstimateExperimentalResponseMessage { 964 | RpcFeeEstimate estimate = 1; 965 | RpcFeeEstimateVerboseExperimentalData verbose = 2; 966 | 967 | RPCError error = 1000; 968 | } 969 | 970 | message GetCurrentBlockColorRequestMessage { 971 | string hash = 1; 972 | } 973 | 974 | message GetCurrentBlockColorResponseMessage { 975 | bool blue = 1; 976 | 977 | RPCError error = 1000; 978 | } 979 | 980 | message GetUtxoReturnAddressRequestMessage { 981 | string txid = 1; 982 | uint64 accepting_block_daa_score = 2; 983 | } 984 | 985 | message GetUtxoReturnAddressResponseMessage { 986 | string return_address = 1; 987 | RPCError error = 1000; 988 | } -------------------------------------------------------------------------------- /sf-class2-root.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl 3 | MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp 4 | U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw 5 | NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE 6 | ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp 7 | ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 8 | DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf 9 | 8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN 10 | +lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 11 | X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa 12 | K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA 13 | 1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G 14 | A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR 15 | zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 16 | YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD 17 | bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w 18 | DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 19 | L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D 20 | eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl 21 | xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp 22 | VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY 23 | WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /storage/cassa.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package storage 4 | 5 | import ( 6 | "sync" 7 | "time" 8 | "math" 9 | "log/slog" 10 | "github.com/gocql/gocql" 11 | ) 12 | 13 | //////////////////////////////// 14 | const numConns = 10 15 | const inMaxCassa = 10 16 | const nQueryCassa = 5 17 | const nBatchMaxCassa = 200 18 | const mtsDelayExecuteCassa = 5 19 | const mtsDelayQueryCassa = 5 20 | 21 | //////////////////////////////// 22 | var mtsBatchLastCassa = int64(0) 23 | 24 | //////////////////////////////// 25 | func startExecuteBatchCassa(lenBatch int, fAdd func(*gocql.Batch, int) (error)) (int64, error) { 26 | if lenBatch <= 0 { 27 | return 0, nil 28 | } 29 | mtss := time.Now().UnixMilli() 30 | mtsBatchLastCassa = mtss 31 | nStart := 0 32 | nBatchAdj := nBatchMaxCassa 33 | mtsDelay := mtsDelayExecuteCassa 34 | nRetry := 0 35 | for { 36 | nStartNext, err := doExecuteBatchCassa(lenBatch, nStart, fAdd, nBatchAdj) 37 | if err != nil { 38 | nRetry ++ 39 | if nRetry > nBatchMaxCassa { 40 | return 0, err 41 | } 42 | nBatchAdj -- 43 | if nBatchAdj < 1 { 44 | nBatchAdj = 1 45 | } 46 | mtsDelay = mtsDelay * (10+nRetry*2) / 10 47 | if mtsDelay > 1000 { 48 | mtsDelay = 1000 49 | } 50 | } else { 51 | nStart = nStartNext 52 | nRetry = 0 53 | nBatchAdj += 3 54 | if nBatchAdj > nBatchMaxCassa { 55 | nBatchAdj = nBatchMaxCassa 56 | } 57 | mtsDelay = mtsDelay * 8 / 10 58 | if mtsDelay < mtsDelayExecuteCassa { 59 | mtsDelay = mtsDelayExecuteCassa 60 | } 61 | } 62 | if nStart < 0 { 63 | break 64 | } 65 | mtsNow := time.Now().UnixMilli() 66 | if mtsNow - mtsBatchLastCassa >= 1000 { 67 | mtsBatchLastCassa = mtsNow 68 | slog.Debug("storage.doExecuteBatchCassa", "nStartNext", nStart) 69 | } 70 | time.Sleep(time.Duration(mtsDelay) * time.Millisecond) 71 | } 72 | return time.Now().UnixMilli() - mtss, nil 73 | } 74 | 75 | //////////////////////////////// 76 | func doExecuteBatchCassa(lenBatch int, nStart int, fAdd func(*gocql.Batch, int) (error), nBatchAdj int) (int, error) { 77 | nBatch := int(math.Ceil(float64(lenBatch-nStart) / float64(nQueryCassa))) 78 | nStartNext := -1 79 | if nBatch > nBatchAdj { 80 | nBatch = nBatchAdj 81 | nStartNext = nQueryCassa * nBatch + nStart 82 | } 83 | wg := &sync.WaitGroup{} 84 | errList := make(chan error, nBatch) 85 | for i := 0; i < nBatch; i ++ { 86 | batch := sRuntime.sessionCassa.NewBatch(gocql.UnloggedBatch) 87 | for j := nStart+i*nQueryCassa; j < nStart+(i+1)*nQueryCassa; j ++ { 88 | if j >= lenBatch { 89 | break 90 | } 91 | err := fAdd(batch, j) 92 | if err != nil { 93 | return nStartNext, err 94 | } 95 | } 96 | wg.Add(1) 97 | go func() { 98 | err := sRuntime.sessionCassa.ExecuteBatch(batch) 99 | if err != nil { 100 | errList <- err 101 | } 102 | wg.Done() 103 | }() 104 | } 105 | wg.Wait() 106 | if len(errList) > 0 { 107 | err := <- errList 108 | return nStartNext, err 109 | } 110 | return nStartNext, nil 111 | } 112 | 113 | //////////////////////////////// 114 | func startQueryBatchInCassa(lenBatch int, fQuery func(int, int, *gocql.Session) (error)) (int64, error) { 115 | if lenBatch <= 0 { 116 | return 0, nil 117 | } 118 | mtss := time.Now().UnixMilli() 119 | mtsBatchLastCassa = mtss 120 | nStart := 0 121 | nBatchAdj := nBatchMaxCassa 122 | mtsDelay := mtsDelayQueryCassa 123 | nRetry := 0 124 | for { 125 | nStartNext, err := doQueryBatchInCassa(lenBatch, nStart, fQuery, nBatchAdj) 126 | if err != nil { 127 | nRetry ++ 128 | if nRetry > nBatchMaxCassa { 129 | return 0, err 130 | } 131 | nBatchAdj -- 132 | if nBatchAdj < 1 { 133 | nBatchAdj = 1 134 | } 135 | mtsDelay = mtsDelay * (10+nRetry*2) / 10 136 | if mtsDelay > 1000 { 137 | mtsDelay = 1000 138 | } 139 | } else { 140 | nStart = nStartNext 141 | nRetry = 0 142 | nBatchAdj += 3 143 | if nBatchAdj > nBatchMaxCassa { 144 | nBatchAdj = nBatchMaxCassa 145 | } 146 | mtsDelay = mtsDelay * 8 / 10 147 | if mtsDelay < mtsDelayQueryCassa { 148 | mtsDelay = mtsDelayQueryCassa 149 | } 150 | } 151 | if nStart < 0 { 152 | break 153 | } 154 | mtsNow := time.Now().UnixMilli() 155 | if mtsNow - mtsBatchLastCassa >= 1000 { 156 | mtsBatchLastCassa = mtsNow 157 | slog.Debug("storage.doQueryBatchInCassa", "nStartNext", nStart) 158 | } 159 | time.Sleep(time.Duration(mtsDelay) * time.Millisecond) 160 | } 161 | return time.Now().UnixMilli() - mtss, nil 162 | } 163 | 164 | //////////////////////////////// 165 | func doQueryBatchInCassa(lenBatch int, nStart int, fQuery func(int, int, *gocql.Session) (error), nBatchAdj int) (int, error) { 166 | nBatch := int(math.Ceil(float64(lenBatch-nStart) / float64(inMaxCassa))) 167 | nStartNext := -1 168 | if nBatch > nBatchMaxCassa { 169 | nBatch = nBatchMaxCassa 170 | nStartNext = inMaxCassa * nBatch + nStart 171 | } 172 | wg := &sync.WaitGroup{} 173 | errList := make(chan error, nBatch) 174 | for i := 0; i < nBatch; i ++ { 175 | iStart := nStart + i*inMaxCassa 176 | iEnd := nStart + (i+1)*inMaxCassa 177 | if iEnd >= lenBatch { 178 | iEnd = lenBatch 179 | } 180 | wg.Add(1) 181 | go func() { 182 | err := fQuery(iStart, iEnd, sRuntime.sessionCassa) 183 | if err != nil { 184 | errList <- err 185 | } 186 | wg.Done() 187 | }() 188 | } 189 | wg.Wait() 190 | if len(errList) > 0 { 191 | err := <- errList 192 | return nStartNext, err 193 | } 194 | return nStartNext, nil 195 | } -------------------------------------------------------------------------------- /storage/cql.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package storage 4 | 5 | var ( 6 | //////////////////////////// 7 | cqlnInitTable = []string { 8 | // v2.01 9 | "CREATE TABLE IF NOT EXISTS sttoken(p2tick ascii, tick ascii, meta ascii, minted ascii, opmod bigint, mtsmod bigint, PRIMARY KEY((p2tick), tick)) WITH CLUSTERING ORDER BY(tick ASC);", 10 | "CREATE TABLE IF NOT EXISTS stbalance(address ascii, tick ascii, dec tinyint, balance ascii, locked ascii, opmod bigint, PRIMARY KEY((address), tick)) WITH CLUSTERING ORDER BY(tick ASC);", 11 | "CREATE TABLE IF NOT EXISTS oplist(oprange bigint, opscore bigint, txid ascii, state ascii, script ascii, tickaffc ascii, addressaffc ascii, PRIMARY KEY((oprange), opscore)) WITH CLUSTERING ORDER BY(opscore ASC);", 12 | "CREATE TABLE IF NOT EXISTS opdata(txid ascii, state ascii, script ascii, stbefore ascii, stafter ascii, PRIMARY KEY((txid)));", 13 | // v2.02 14 | "CREATE TABLE IF NOT EXISTS stmarket(tick ascii, taddr_utxid ascii, uaddr ascii, uamt ascii, uscript ascii, tamt ascii, opadd bigint, PRIMARY KEY((tick), taddr_utxid)) WITH CLUSTERING ORDER BY(taddr_utxid ASC);", 15 | // v2.03 16 | "CREATE TABLE IF NOT EXISTS stblacklist(tick ascii, address ascii, opadd bigint, PRIMARY KEY((tick), address)) WITH CLUSTERING ORDER BY(address ASC);", 17 | "ALTER TABLE sttoken ADD (mod ascii, burned ascii);", 18 | // ... 19 | } 20 | //////////////////////////// 21 | cqlnGetRuntime = "SELECT * FROM runtime WHERE key=?;" 22 | cqlnSetRuntime = "INSERT INTO runtime (key,value1,value2,value3) VALUES (?,?,?,?);" 23 | //////////////////////////// 24 | cqlnGetVspcData = "SELECT daascore,hash,txid FROM vspc WHERE daascore IN ({daascoreIn});" 25 | //////////////////////////// 26 | cqlnGetTransactionData = "SELECT txid,data FROM transaction WHERE txid IN ({txidIn});" 27 | //////////////////////////// 28 | cqlnSaveStateToken = "INSERT INTO sttoken (p2tick,tick,meta,minted,opmod,mtsmod,mod,burned) VALUES (?,?,?,?,?,?,?,?);" 29 | cqlnDeleteStateToken = "DELETE FROM sttoken WHERE p2tick=? AND tick=?;" 30 | cqlnSaveStateBalance = "INSERT INTO stbalance (address,tick,dec,balance,locked,opmod) VALUES (?,?,?,?,?,?);" 31 | cqlnDeleteStateBalance = "DELETE FROM stbalance WHERE address=? AND tick=?;" 32 | cqlnSaveStateMarket = "INSERT INTO stmarket (tick,taddr_utxid,uaddr,uamt,uscript,tamt,opadd) VALUES (?,?,?,?,?,?,?);" 33 | cqlnDeleteStateMarket = "DELETE FROM stmarket WHERE tick=? AND taddr_utxid=?;" 34 | cqlnSaveStateBlacklist = "INSERT INTO stblacklist (tick,address,opadd) VALUES (?,?,?);" 35 | cqlnDeleteStateBlacklist = "DELETE FROM stblacklist WHERE tick=? AND address=?;" 36 | //////////////////////////// 37 | cqlnSaveOpData = "INSERT INTO opdata (txid,state,script,stbefore,stafter) VALUES (?,?,?,?,?);" 38 | cqlnDeleteOpData = "DELETE FROM opdata WHERE txid=?;" 39 | //////////////////////////// 40 | cqlnSaveOpList = "INSERT INTO oplist (oprange,opscore,txid,state,script,tickaffc,addressaffc) VALUES (?,?,?,?,?,?,?);" 41 | cqlnDeleteOpList = "DELETE FROM oplist WHERE oprange=? AND opscore=?;" 42 | // ... 43 | ) 44 | -------------------------------------------------------------------------------- /storage/init.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package storage 4 | 5 | import ( 6 | "log" 7 | "time" 8 | "strings" 9 | "log/slog" 10 | "math/rand" 11 | "github.com/gocql/gocql" 12 | "github.com/tecbot/gorocksdb" 13 | "kasplex-executor/config" 14 | ) 15 | 16 | //////////////////////////////// 17 | type runtimeType struct { 18 | cassa *gocql.ClusterConfig 19 | sessionCassa *gocql.Session 20 | rocksTx *gorocksdb.TransactionDB 21 | rOptRocks *gorocksdb.ReadOptions 22 | wOptRocks *gorocksdb.WriteOptions 23 | txOptRocks *gorocksdb.TransactionOptions 24 | cfgCassa config.CassaConfig 25 | cfgRocks config.RocksConfig 26 | // ... 27 | } 28 | var sRuntime runtimeType 29 | 30 | //////////////////////////////// 31 | func Init(cfgCassa config.CassaConfig, cfgRocks config.RocksConfig) { 32 | rand.Seed(time.Now().UnixNano()) 33 | sRuntime.cfgCassa = cfgCassa 34 | sRuntime.cfgRocks = cfgRocks 35 | slog.Info("storage.Init start.") 36 | 37 | // Use cassandra driver. 38 | var err error 39 | sRuntime.cassa = gocql.NewCluster(sRuntime.cfgCassa.Host) 40 | sRuntime.cassa.Port = sRuntime.cfgCassa.Port 41 | sRuntime.cassa.Authenticator = gocql.PasswordAuthenticator{ 42 | Username: sRuntime.cfgCassa.User, 43 | Password: sRuntime.cfgCassa.Pass, 44 | } 45 | if sRuntime.cfgCassa.Crt != "" { 46 | sRuntime.cassa.SslOpts = &gocql.SslOptions{ 47 | CaPath: sRuntime.cfgCassa.Crt, 48 | EnableHostVerification: false, 49 | } 50 | } 51 | sRuntime.cassa.Consistency = gocql.LocalQuorum 52 | sRuntime.cassa.DisableInitialHostLookup = false 53 | sRuntime.cassa.NumConns = numConns 54 | sRuntime.cassa.Keyspace = sRuntime.cfgCassa.Space 55 | sRuntime.sessionCassa, err = sRuntime.cassa.CreateSession() 56 | if err != nil { 57 | log.Fatalln("storage.Init fatal: ", err.Error()) 58 | } 59 | 60 | // Init database if new installation. 61 | for _, cqln := range cqlnInitTable { 62 | err = sRuntime.sessionCassa.Query(cqln).Exec() 63 | if err != nil { 64 | msg := err.Error() 65 | if strings.HasSuffix(msg, "conflicts with an existing column") || strings.HasSuffix(msg, "already exists") { 66 | continue 67 | } 68 | log.Fatalln("storage.Init fatal:", err.Error()) 69 | } 70 | } 71 | 72 | // Use rocksdb driver. 73 | sRuntime.rOptRocks = gorocksdb.NewDefaultReadOptions() 74 | sRuntime.wOptRocks = gorocksdb.NewDefaultWriteOptions() 75 | sRuntime.txOptRocks = gorocksdb.NewDefaultTransactionOptions() 76 | optRocks := gorocksdb.NewDefaultOptions() 77 | optRocks.SetUseFsync(true) 78 | optRocks.SetCreateIfMissing(true) 79 | optRocks.SetCreateIfMissingColumnFamilies(true) 80 | optRocks.SetWriteBufferSize(256 * 1024 * 1024) 81 | optRocks.SetMaxWriteBufferNumber(4) 82 | optRocks.SetMaxBackgroundCompactions(4) 83 | optBbtRocks := gorocksdb.NewDefaultBlockBasedTableOptions() 84 | optBbtRocks.SetBlockSize(8 * 1024) 85 | optBbtRocks.SetBlockCache(gorocksdb.NewLRUCache(1024 * 1024 * 1024)) 86 | optBbtRocks.SetFilterPolicy(gorocksdb.NewBloomFilter(10)) 87 | optBbtRocks.SetCacheIndexAndFilterBlocks(true) 88 | optRocks.SetBlockBasedTableFactory(optBbtRocks) 89 | txOptRocks := gorocksdb.NewDefaultTransactionDBOptions() 90 | txOptRocks.SetTransactionLockTimeout(10) 91 | sRuntime.rocksTx, err = gorocksdb.OpenTransactionDb(optRocks, txOptRocks, sRuntime.cfgRocks.Path) 92 | if err != nil { 93 | log.Fatalln("storage.Init fatal: ", err.Error()) 94 | } 95 | 96 | slog.Info("storage ready.") 97 | } 98 | 99 | -------------------------------------------------------------------------------- /storage/node.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package storage 4 | 5 | import ( 6 | "sync" 7 | "sort" 8 | "strconv" 9 | "strings" 10 | //"log/slog" 11 | "encoding/json" 12 | "github.com/gocql/gocql" 13 | "kasplex-executor/protowire" 14 | ) 15 | 16 | //////////////////////////////// 17 | // Get the next vspc data list, use the node archive db. 18 | func GetNodeVspcList(daaScoreStart uint64, lenBlock int) ([]DataVspcType, int64, error) { 19 | vspcMap := map[uint64]*DataVspcType{} 20 | mutex := new(sync.RWMutex) 21 | mtsBatch, err := startQueryBatchInCassa(lenBlock, func(iStart int, iEnd int, session *gocql.Session) (error) { 22 | daaScoreList := []string{} 23 | for i := iStart; i < iEnd; i ++ { 24 | daaScoreList = append(daaScoreList, strconv.FormatUint(daaScoreStart+uint64(i),10)) 25 | } 26 | daaScoreIn := strings.Join(daaScoreList, ",") 27 | cql := strings.Replace(cqlnGetVspcData, "{daascoreIn}", daaScoreIn, 1) 28 | row := session.Query(cql).Iter().Scanner() 29 | for row.Next() { 30 | var daaScore uint64 31 | var hash string 32 | var txId string 33 | err := row.Scan(&daaScore, &hash, &txId) 34 | if err != nil { 35 | return err 36 | } 37 | txIdList := strings.Split(txId, ",") 38 | if daaScore < 110165000 { 39 | sort.Strings(txIdList) 40 | } 41 | mutex.Lock() 42 | vspcMap[daaScore] = &DataVspcType{ 43 | DaaScore: daaScore, 44 | Hash: hash, 45 | TxIdList: txIdList, 46 | } 47 | mutex.Unlock() 48 | } 49 | return row.Err() 50 | }) 51 | if err != nil { 52 | return nil, 0, err 53 | } 54 | vspcList := []DataVspcType{} 55 | for i := daaScoreStart; i < daaScoreStart+uint64(lenBlock); i ++ { 56 | if vspcMap[i] == nil { 57 | continue 58 | } 59 | vspcList = append(vspcList, *vspcMap[i]) 60 | } 61 | return vspcList, mtsBatch, nil 62 | } 63 | 64 | //////////////////////////////// 65 | // Get the data map of the transaction in vspc, use the node archive db. 66 | func GetNodeTransactionDataMap(txDataList []DataTransactionType) (map[string]*protowire.RpcTransaction, int64, error) { 67 | txDataMap := map[string]*protowire.RpcTransaction{} 68 | mutex := new(sync.RWMutex) 69 | mtsBatch, err := startQueryBatchInCassa(len(txDataList), func(iStart int, iEnd int, session *gocql.Session) (error) { 70 | txIdList := []string{} 71 | for i := iStart; i < iEnd; i ++ { 72 | txIdList = append(txIdList, "'"+txDataList[i].TxId+"'") 73 | } 74 | txIdIn := strings.Join(txIdList, ",") 75 | cql := strings.Replace(cqlnGetTransactionData, "{txidIn}", txIdIn, 1) 76 | row := session.Query(cql).Iter().Scanner() 77 | for row.Next() { 78 | var txId string 79 | var dataJson string 80 | err := row.Scan(&txId, &dataJson) 81 | if err != nil { 82 | return err 83 | } 84 | data := protowire.RpcTransaction{} 85 | err = json.Unmarshal([]byte(dataJson), &data) 86 | if err != nil { 87 | return err 88 | } 89 | mutex.Lock() 90 | txDataMap[txId] = &data 91 | mutex.Unlock() 92 | } 93 | return row.Err() 94 | }) 95 | if err != nil { 96 | return nil, 0, err 97 | } 98 | return txDataMap, mtsBatch, nil 99 | } 100 | 101 | //////////////////////////////// 102 | // Get the data list of the transaction in vspc, use the node archive db. 103 | func GetNodeTransactionDataList(txDataList []DataTransactionType) ([]DataTransactionType, int64, error) { 104 | txDataMap, mtsBatch, err := GetNodeTransactionDataMap(txDataList) 105 | if err != nil { 106 | return nil, 0, err 107 | } 108 | for i, txData := range txDataList { 109 | if txDataMap[txData.TxId] == nil { 110 | continue 111 | } 112 | txDataList[i].Data = txDataMap[txData.TxId] 113 | } 114 | return txDataList, mtsBatch, nil 115 | } 116 | 117 | // ... 118 | -------------------------------------------------------------------------------- /storage/rocks.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package storage 4 | 5 | import ( 6 | "sync" 7 | "time" 8 | "math" 9 | //"log/slog" 10 | "github.com/tecbot/gorocksdb" 11 | ) 12 | 13 | //////////////////////////////// 14 | const nWriteRocks = 100 15 | const nGetRocks = 100 16 | const nBatchMaxRocks = 100 17 | 18 | //////////////////////////////// 19 | var mtsBatchLastRocks = int64(0) 20 | 21 | //////////////////////////////// 22 | func doGetBatchRocks(lenBatch int, nStart int, fGet func(int, int, *gorocksdb.TransactionDB, *gorocksdb.ReadOptions) (error)) (int64, error) { 23 | if lenBatch <= 0 { 24 | return 0, nil 25 | } 26 | mtss := time.Now().UnixMilli() 27 | if nStart == 0 { 28 | mtsBatchLastRocks = mtss 29 | } 30 | nBatch := int(math.Ceil(float64(lenBatch-nStart) / float64(nGetRocks))) 31 | nStartNext := 0 32 | if nBatch > nBatchMaxRocks { 33 | nBatch = nBatchMaxRocks 34 | nStartNext = nGetRocks * nBatch + nStart 35 | } 36 | wg := &sync.WaitGroup{} 37 | errList := make(chan error, nBatch) 38 | for i := 0; i < nBatch; i ++ { 39 | iStart := nStart + i*nGetRocks 40 | iEnd := nStart + (i+1)*nGetRocks 41 | if iEnd >= lenBatch { 42 | iEnd = lenBatch 43 | } 44 | wg.Add(1) 45 | go func() { 46 | err := fGet(iStart, iEnd, sRuntime.rocksTx, sRuntime.rOptRocks) 47 | if err != nil { 48 | errList <- err 49 | } 50 | wg.Done() 51 | }() 52 | } 53 | wg.Wait() 54 | if len(errList) > 0 { 55 | err := <- errList 56 | return 0, err 57 | } 58 | if nStartNext > 0 { 59 | _, err := doGetBatchRocks(lenBatch, nStartNext, fGet) 60 | if err != nil { 61 | return 0, err 62 | } 63 | } 64 | return time.Now().UnixMilli() - mtss, nil 65 | } 66 | -------------------------------------------------------------------------------- /storage/runtime.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package storage 4 | 5 | import ( 6 | "strconv" 7 | "encoding/json" 8 | ) 9 | 10 | //////////////////////////////// 11 | const keyPrefixRuntime = "RTA_" // runtime-arguments 12 | const keyPrefixRuntimeCassa = "EXE_" // runtime-arguments in the cluster db. 13 | 14 | //////////////////////////////// 15 | // Get runtime data by key, in the local db. 16 | func GetRuntimeRocks(key string) ([]byte, error) { 17 | key = keyPrefixRuntime + key 18 | row, err := sRuntime.rocksTx.Get(sRuntime.rOptRocks, []byte(key)) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return row.Data(), nil 23 | } 24 | 25 | //////////////////////////////// 26 | // Set runtime data by key, in the local db. 27 | func SetRuntimeRocks(key string, valueJson []byte) (error) { 28 | key = keyPrefixRuntime + key 29 | err := sRuntime.rocksTx.Put(sRuntime.wOptRocks, []byte(key), valueJson) 30 | return err 31 | } 32 | 33 | //////////////////////////////// 34 | // Get the last processed vspc data list. 35 | func GetRuntimeVspcLast() ([]DataVspcType, error) { 36 | valueJson, err := GetRuntimeRocks("VSPCLAST") 37 | if err != nil { 38 | return nil, err 39 | } 40 | if len(valueJson) <= 0 { 41 | return nil, nil 42 | } 43 | list := []DataVspcType{} 44 | err = json.Unmarshal(valueJson, &list) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return list, err 49 | } 50 | 51 | //////////////////////////////// 52 | // Set the last processed vspc data list. 53 | func SetRuntimeVspcLast(list []DataVspcType) (error) { 54 | valueJson, _ := json.Marshal(list) 55 | err := SetRuntimeRocks("VSPCLAST", valueJson) 56 | return err 57 | } 58 | 59 | //////////////////////////////// 60 | // Get the last rollback data list. 61 | func GetRuntimeRollbackLast() ([]DataRollbackType, error) { 62 | valueJson, err := GetRuntimeRocks("ROLLBACKLAST") 63 | if err != nil { 64 | return nil, err 65 | } 66 | if len(valueJson) <= 0 { 67 | return nil, nil 68 | } 69 | list := []DataRollbackType{} 70 | err = json.Unmarshal(valueJson, &list) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return list, err 75 | } 76 | 77 | //////////////////////////////// 78 | // Set the last op data list. 79 | func SetRuntimeRollbackLast(list []DataRollbackType) (error) { 80 | valueJson, _ := json.Marshal(list) 81 | err := SetRuntimeRocks("ROLLBACKLAST", valueJson) 82 | return err 83 | } 84 | 85 | //////////////////////////////// 86 | // Get runtime data from table "runtime", in the cluster db. 87 | func GetRuntimeCassa(key string) (string, string, string, error) { 88 | key = keyPrefixRuntimeCassa + key 89 | row := sRuntime.sessionCassa.Query(cqlnGetRuntime, key) 90 | defer row.Release() 91 | var k0, v1, v2, v3 string 92 | err := row.Scan(&k0, &v1, &v2, &v3) 93 | if err != nil { 94 | if err.Error() == "not found"{ 95 | return "", "", "", nil 96 | } 97 | return "", "", "", err 98 | } 99 | return v1, v2, v3, nil 100 | } 101 | 102 | //////////////////////////////// 103 | // Set runtime data to table "runtime", in the cluster db. 104 | func SetRuntimeCassa(key string, v1 string, v2 string, v3 string) (error) { 105 | key = keyPrefixRuntimeCassa + key 106 | err := sRuntime.sessionCassa.Query(cqlnSetRuntime, key, v1, v2, v3).Exec() 107 | return err 108 | } 109 | 110 | //////////////////////////////// 111 | // Get the sync state. 112 | func GetRuntimeSynced() (bool, uint64, error) { 113 | Synced, _, strDaaScore, err := GetRuntimeCassa("SYNCED") 114 | if err != nil { 115 | return false, 0, err 116 | } 117 | daaScore, _ := strconv.ParseUint(strDaaScore, 10, 64) 118 | if Synced == "" { 119 | return false, daaScore, nil 120 | } 121 | return true, daaScore, nil 122 | } 123 | 124 | //////////////////////////////// 125 | // Set the sync state. 126 | func SetRuntimeSynced(Synced bool, opScore uint64, daaScore uint64) (error) { 127 | var err error 128 | strDaaScore := strconv.FormatUint(daaScore, 10) 129 | strOpScore := strconv.FormatUint(opScore, 10) 130 | if Synced { 131 | err = SetRuntimeCassa("SYNCED", "1", strOpScore, strDaaScore) 132 | } else { 133 | err = SetRuntimeCassa("SYNCED", "", strOpScore, strDaaScore) 134 | } 135 | return err 136 | } 137 | 138 | //////////////////////////////// 139 | // Set the version. 140 | func SetRuntimeVersion(version string) (error) { 141 | return SetRuntimeCassa("VERSION", version, "", "") 142 | } 143 | 144 | // ... 145 | -------------------------------------------------------------------------------- /storage/state.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package storage 4 | 5 | import ( 6 | "sync" 7 | "time" 8 | "strings" 9 | //"log/slog" 10 | "math/rand" 11 | "encoding/json" 12 | "github.com/gocql/gocql" 13 | "github.com/tecbot/gorocksdb" 14 | ) 15 | 16 | //////////////////////////////// 17 | const OpRangeBy = uint64(100000) 18 | 19 | //////////////////////////////// 20 | const KeyPrefixStateToken = "sttoken_" 21 | const KeyPrefixStateBalance = "stbalance_" 22 | const KeyPrefixStateMarket = "stmarket_" 23 | const KeyPrefixStateBlacklist = "stblacklist_" 24 | // KeyPrefixStateXxx ... 25 | 26 | //////////////////////////////// 27 | func GetStateTokenMap(tokenMap map[string]*StateTokenType) (int64, error) { 28 | keyList := [][]byte{} 29 | for tick := range tokenMap { 30 | keyList = append(keyList, []byte(KeyPrefixStateToken+tick)) 31 | } 32 | mutex := new(sync.RWMutex) 33 | mtsBatch, err := doGetBatchRocks(len(keyList), 0, func(iStart int, iEnd int, rdb *gorocksdb.TransactionDB, rro *gorocksdb.ReadOptions) (error) { 34 | for i := iStart; i < iEnd; i ++ { 35 | row, err := rdb.Get(rro, keyList[i]) 36 | if err != nil { 37 | return err 38 | } 39 | dataByte := row.Data() 40 | if dataByte == nil { 41 | continue 42 | } 43 | decoded := StateTokenType{} 44 | err = json.Unmarshal(dataByte, &decoded) 45 | if err != nil { 46 | return err 47 | } 48 | mutex.Lock() 49 | tokenMap[decoded.Tick] = &decoded 50 | mutex.Unlock() 51 | } 52 | return nil 53 | }) 54 | if err != nil { 55 | return 0, err 56 | } 57 | return mtsBatch, nil 58 | } 59 | 60 | //////////////////////////////// 61 | func GetStateBalanceMap(balanceMap map[string]*StateBalanceType) (int64, error) { 62 | keyList := [][]byte{} 63 | for addrTick := range balanceMap { 64 | keyList = append(keyList, []byte(KeyPrefixStateBalance+addrTick)) 65 | } 66 | mutex := new(sync.RWMutex) 67 | mtsBatch, err := doGetBatchRocks(len(keyList), 0, func(iStart int, iEnd int, rdb *gorocksdb.TransactionDB, rro *gorocksdb.ReadOptions) (error) { 68 | for i := iStart; i < iEnd; i ++ { 69 | row, err := rdb.Get(rro, keyList[i]) 70 | if err != nil { 71 | return err 72 | } 73 | dataByte := row.Data() 74 | if dataByte == nil { 75 | continue 76 | } 77 | decoded := StateBalanceType{} 78 | err = json.Unmarshal(dataByte, &decoded) 79 | if err != nil { 80 | return err 81 | } 82 | mutex.Lock() 83 | balanceMap[decoded.Address+"_"+decoded.Tick] = &decoded 84 | mutex.Unlock() 85 | } 86 | return nil 87 | }) 88 | if err != nil { 89 | return 0, err 90 | } 91 | return mtsBatch, nil 92 | } 93 | 94 | //////////////////////////////// 95 | func GetStateMarketMap(marketMap map[string]*StateMarketType) (int64, error) { 96 | keyList := [][]byte{} 97 | for tickAddrTxid := range marketMap { 98 | keyList = append(keyList, []byte(KeyPrefixStateMarket+tickAddrTxid)) 99 | } 100 | mutex := new(sync.RWMutex) 101 | mtsBatch, err := doGetBatchRocks(len(keyList), 0, func(iStart int, iEnd int, rdb *gorocksdb.TransactionDB, rro *gorocksdb.ReadOptions) (error) { 102 | for i := iStart; i < iEnd; i ++ { 103 | row, err := rdb.Get(rro, keyList[i]) 104 | if err != nil { 105 | return err 106 | } 107 | dataByte := row.Data() 108 | if dataByte == nil { 109 | continue 110 | } 111 | decoded := StateMarketType{} 112 | err = json.Unmarshal(dataByte, &decoded) 113 | if err != nil { 114 | return err 115 | } 116 | mutex.Lock() 117 | marketMap[decoded.Tick+"_"+decoded.TAddr+"_"+decoded.UTxId] = &decoded 118 | mutex.Unlock() 119 | } 120 | return nil 121 | }) 122 | if err != nil { 123 | return 0, err 124 | } 125 | return mtsBatch, nil 126 | } 127 | 128 | //////////////////////////////// 129 | func GetStateBlacklistMap(blacklistMap map[string]*StateBlacklistType) (int64, error) { 130 | keyList := [][]byte{} 131 | for tickAddr := range blacklistMap { 132 | keyList = append(keyList, []byte(KeyPrefixStateBlacklist+tickAddr)) 133 | } 134 | mutex := new(sync.RWMutex) 135 | mtsBatch, err := doGetBatchRocks(len(keyList), 0, func(iStart int, iEnd int, rdb *gorocksdb.TransactionDB, rro *gorocksdb.ReadOptions) (error) { 136 | for i := iStart; i < iEnd; i ++ { 137 | row, err := rdb.Get(rro, keyList[i]) 138 | if err != nil { 139 | return err 140 | } 141 | dataByte := row.Data() 142 | if dataByte == nil { 143 | continue 144 | } 145 | decoded := StateBlacklistType{} 146 | err = json.Unmarshal(dataByte, &decoded) 147 | if err != nil { 148 | return err 149 | } 150 | mutex.Lock() 151 | blacklistMap[decoded.Tick+"_"+decoded.Address] = &decoded 152 | mutex.Unlock() 153 | } 154 | return nil 155 | }) 156 | if err != nil { 157 | return 0, err 158 | } 159 | return mtsBatch, nil 160 | } 161 | 162 | //////////////////////////////// 163 | // GetStateXxx ... 164 | 165 | //////////////////////////////// 166 | func CopyDataStateMap(stateMapFrom DataStateMapType, stateMapTo *DataStateMapType) { 167 | stateMapTo.StateTokenMap = make(map[string]*StateTokenType) 168 | stateMapTo.StateBalanceMap = make(map[string]*StateBalanceType) 169 | stateMapTo.StateMarketMap = make(map[string]*StateMarketType) 170 | stateMapTo.StateBlacklistMap = make(map[string]*StateBlacklistType) 171 | // stateMapTo.StateXxxMap ... 172 | for key, stToken := range stateMapFrom.StateTokenMap { 173 | if stToken == nil { 174 | stateMapTo.StateTokenMap[key] = nil 175 | continue 176 | } 177 | stData := *stToken 178 | stateMapTo.StateTokenMap[key] = &stData 179 | } 180 | for key, stBalance := range stateMapFrom.StateBalanceMap { 181 | if stBalance == nil { 182 | stateMapTo.StateBalanceMap[key] = nil 183 | continue 184 | } 185 | stData := *stBalance 186 | stateMapTo.StateBalanceMap[key] = &stData 187 | } 188 | for key, stMarket := range stateMapFrom.StateMarketMap { 189 | if stMarket == nil { 190 | stateMapTo.StateMarketMap[key] = nil 191 | continue 192 | } 193 | stData := *stMarket 194 | stateMapTo.StateMarketMap[key] = &stData 195 | } 196 | for key, stBlacklist := range stateMapFrom.StateBlacklistMap { 197 | if stBlacklist == nil { 198 | stateMapTo.StateBlacklistMap[key] = nil 199 | continue 200 | } 201 | stData := *stBlacklist 202 | stateMapTo.StateBlacklistMap[key] = &stData 203 | } 204 | // StateXxx ... 205 | } 206 | 207 | //////////////////////////////// 208 | func SaveStateBatchCassa(stateMap DataStateMapType) (int64, error) { 209 | mtss := time.Now().UnixMilli() 210 | keyList := make([]string, 0, len(stateMap.StateTokenMap)) 211 | for key := range stateMap.StateTokenMap { 212 | keyList = append(keyList, key) 213 | } 214 | _, err := startExecuteBatchCassa(len(keyList), func(batch *gocql.Batch, i int) (error) { 215 | stToken := stateMap.StateTokenMap[keyList[i]] 216 | tick := keyList[i] 217 | if stToken == nil { 218 | batch.Query(cqlnDeleteStateToken, tick[:2], tick) 219 | return nil 220 | } 221 | meta := &StateTokenMetaType{ 222 | Max: stToken.Max, 223 | Lim: stToken.Lim, 224 | Pre: stToken.Pre, 225 | Dec: stToken.Dec, 226 | From: stToken.From, 227 | To: stToken.To, 228 | Name: stToken.Name, 229 | TxId: stToken.TxId, 230 | OpAdd: stToken.OpAdd, 231 | MtsAdd: stToken.MtsAdd, 232 | } 233 | metaJson, _ := json.Marshal(meta) 234 | batch.Query(cqlnSaveStateToken, tick[:2], tick, string(metaJson), stToken.Minted, stToken.OpMod, stToken.MtsMod, stToken.Mod, stToken.Burned) 235 | return nil 236 | }) 237 | if err != nil { 238 | return 0, err 239 | } 240 | keyList = make([]string, 0, len(stateMap.StateBalanceMap)) 241 | for key := range stateMap.StateBalanceMap { 242 | keyList = append(keyList, key) 243 | } 244 | _, err = startExecuteBatchCassa(len(keyList), func(batch *gocql.Batch, i int) (error) { 245 | stBalance := stateMap.StateBalanceMap[keyList[i]] 246 | key := strings.Split(keyList[i], "_") 247 | if stBalance == nil { 248 | batch.Query(cqlnDeleteStateBalance, key[0], key[1]) 249 | return nil 250 | } 251 | batch.Query(cqlnSaveStateBalance, key[0], key[1], stBalance.Dec, stBalance.Balance, stBalance.Locked, stBalance.OpMod) 252 | return nil 253 | }) 254 | if err != nil { 255 | return 0, err 256 | } 257 | keyList = make([]string, 0, len(stateMap.StateMarketMap)) 258 | for key := range stateMap.StateMarketMap { 259 | keyList = append(keyList, key) 260 | } 261 | _, err = startExecuteBatchCassa(len(keyList), func(batch *gocql.Batch, i int) (error) { 262 | stMarket := stateMap.StateMarketMap[keyList[i]] 263 | key := strings.Split(keyList[i], "_") 264 | if stMarket == nil { 265 | batch.Query(cqlnDeleteStateMarket, key[0], key[1]+"_"+key[2]) 266 | return nil 267 | } 268 | batch.Query(cqlnSaveStateMarket, key[0], key[1]+"_"+key[2], stMarket.UAddr, stMarket.UAmt, stMarket.UScript, stMarket.TAmt, stMarket.OpAdd) 269 | return nil 270 | }) 271 | if err != nil { 272 | return 0, err 273 | } 274 | keyList = make([]string, 0, len(stateMap.StateBlacklistMap)) 275 | for key := range stateMap.StateBlacklistMap { 276 | keyList = append(keyList, key) 277 | } 278 | _, err = startExecuteBatchCassa(len(keyList), func(batch *gocql.Batch, i int) (error) { 279 | stBlacklist := stateMap.StateBlacklistMap[keyList[i]] 280 | key := strings.Split(keyList[i], "_") 281 | if stBlacklist == nil { 282 | batch.Query(cqlnDeleteStateBlacklist, key[0], key[1]) 283 | return nil 284 | } 285 | batch.Query(cqlnSaveStateBlacklist, key[0], key[1], stBlacklist.OpAdd) 286 | return nil 287 | }) 288 | if err != nil { 289 | return 0, err 290 | } 291 | // StateXxx ... 292 | return time.Now().UnixMilli() - mtss, nil 293 | } 294 | 295 | //////////////////////////////// 296 | func SaveOpDataBatchCassa(opDataList []DataOperationType) (int64, error) { 297 | mtss := time.Now().UnixMilli() 298 | stateJsonMap := make(map[string]string, len(opDataList)) 299 | scriptJsonMap := make(map[string]string, len(opDataList)) 300 | _, err := startExecuteBatchCassa(len(opDataList), func(batch *gocql.Batch, i int) (error) { 301 | state := &DataOpStateType{ 302 | BlockAccept: opDataList[i].BlockAccept, 303 | Fee: opDataList[i].Fee, 304 | FeeLeast: opDataList[i].FeeLeast, 305 | MtsAdd: opDataList[i].MtsAdd, 306 | OpScore: opDataList[i].OpScore, 307 | OpAccept: opDataList[i].OpAccept, 308 | OpError: opDataList[i].OpError, 309 | Checkpoint: opDataList[i].Checkpoint, 310 | } 311 | stateJson, _ := json.Marshal(state) 312 | scriptJson, _ := json.Marshal(opDataList[i].OpScript[0]) 313 | stBeforeJson, _ := json.Marshal(opDataList[i].StBefore) 314 | stAfterJson, _ := json.Marshal(opDataList[i].StAfter) 315 | stateJsonMap[opDataList[i].TxId] = string(stateJson) 316 | scriptJsonMap[opDataList[i].TxId] = string(scriptJson) 317 | batch.Query(cqlnSaveOpData, opDataList[i].TxId, string(stateJson), string(scriptJson), string(stBeforeJson), string(stAfterJson)) 318 | return nil 319 | }) 320 | if err != nil { 321 | return 0, err 322 | } 323 | rand.Shuffle(len(opDataList), func(i int, j int) { 324 | opDataList[i], opDataList[j] = opDataList[j], opDataList[i] 325 | }) 326 | _, err = startExecuteBatchCassa(len(opDataList), func(batch *gocql.Batch, i int) (error) { 327 | tickAffc := strings.Join(opDataList[i].SsInfo.TickAffc, ",") 328 | addressAffc := strings.Join(opDataList[i].SsInfo.AddressAffc, ",") 329 | // xxxAffc ... 330 | opRange := opDataList[i].OpScore / OpRangeBy 331 | batch.Query(cqlnSaveOpList, opRange, opDataList[i].OpScore, opDataList[i].TxId, stateJsonMap[opDataList[i].TxId], scriptJsonMap[opDataList[i].TxId], tickAffc, addressAffc) 332 | return nil 333 | }) 334 | if err != nil { 335 | return 0, err 336 | } 337 | return time.Now().UnixMilli() - mtss, nil 338 | } 339 | 340 | //////////////////////////////// 341 | func DeleteOpDataBatchCassa(opScoreList []uint64, txIdList []string) (int64, error) { 342 | mtss := time.Now().UnixMilli() 343 | _, err := startExecuteBatchCassa(len(opScoreList), func(batch *gocql.Batch, i int) (error) { 344 | opRange := opScoreList[i] / OpRangeBy 345 | batch.Query(cqlnDeleteOpList, opRange, opScoreList[i]) 346 | return nil 347 | }) 348 | if err != nil { 349 | return 0, err 350 | } 351 | _, err = startExecuteBatchCassa(len(txIdList), func(batch *gocql.Batch, i int) (error) { 352 | batch.Query(cqlnDeleteOpData, txIdList[i]) 353 | return nil 354 | }) 355 | if err != nil { 356 | return 0, err 357 | } 358 | return time.Now().UnixMilli() - mtss, nil 359 | } 360 | 361 | //////////////////////////////// 362 | func SaveStateBatchRocksBegin(stateMap DataStateMapType, txRocks *gorocksdb.Transaction) (*gorocksdb.Transaction, int64, error) { 363 | mtss := time.Now().UnixMilli() 364 | if txRocks == nil { 365 | txRocks = sRuntime.rocksTx.TransactionBegin(sRuntime.wOptRocks, sRuntime.txOptRocks, nil) 366 | } 367 | var err error 368 | var valueJson []byte 369 | for key, token := range stateMap.StateTokenMap { 370 | key = KeyPrefixStateToken + key 371 | if token == nil { 372 | err = txRocks.Delete([]byte(key)) 373 | } else { 374 | valueJson, _ = json.Marshal(token) 375 | err = txRocks.Put([]byte(key), valueJson) 376 | } 377 | if err != nil { 378 | txRocks.Rollback() 379 | return txRocks, 0, err 380 | } 381 | } 382 | for key, balance := range stateMap.StateBalanceMap { 383 | key = KeyPrefixStateBalance + key 384 | if balance == nil { 385 | err = txRocks.Delete([]byte(key)) 386 | } else { 387 | valueJson, _ = json.Marshal(balance) 388 | err = txRocks.Put([]byte(key), valueJson) 389 | } 390 | if err != nil { 391 | txRocks.Rollback() 392 | return txRocks, 0, err 393 | } 394 | } 395 | for key, market := range stateMap.StateMarketMap { 396 | key = KeyPrefixStateMarket + key 397 | if market == nil { 398 | err = txRocks.Delete([]byte(key)) 399 | } else { 400 | valueJson, _ = json.Marshal(market) 401 | err = txRocks.Put([]byte(key), valueJson) 402 | } 403 | if err != nil { 404 | txRocks.Rollback() 405 | return txRocks, 0, err 406 | } 407 | } 408 | for key, blacklist := range stateMap.StateBlacklistMap { 409 | key = KeyPrefixStateBlacklist + key 410 | if blacklist == nil { 411 | err = txRocks.Delete([]byte(key)) 412 | } else { 413 | valueJson, _ = json.Marshal(blacklist) 414 | err = txRocks.Put([]byte(key), valueJson) 415 | } 416 | if err != nil { 417 | txRocks.Rollback() 418 | return txRocks, 0, err 419 | } 420 | } 421 | // StateXxx ... 422 | return txRocks, time.Now().UnixMilli() - mtss, nil 423 | } 424 | 425 | //////////////////////////////// 426 | func SaveOpStateBatch(opDataList []DataOperationType, stateMap DataStateMapType) ([]int64, error) { 427 | mtsBatchList := [4]int64{} 428 | mtsBatchList[0] = time.Now().UnixMilli() 429 | txRocks, _, err := SaveStateBatchRocksBegin(stateMap, nil) 430 | defer txRocks.Destroy() 431 | if err != nil { 432 | return nil, err 433 | } 434 | mtsBatchList[1] = time.Now().UnixMilli() 435 | _, err = SaveStateBatchCassa(stateMap) 436 | if err != nil { 437 | txRocks.Rollback() 438 | return nil, err 439 | } 440 | mtsBatchList[2] = time.Now().UnixMilli() 441 | _, err = SaveOpDataBatchCassa(opDataList) 442 | if err != nil { 443 | txRocks.Rollback() 444 | return nil, err 445 | } 446 | mtsBatchList[3] = time.Now().UnixMilli() 447 | err = txRocks.Commit() 448 | if err != nil { 449 | txRocks.Rollback() 450 | return nil, err 451 | } 452 | mtsBatchList[0] = mtsBatchList[1] - mtsBatchList[0] 453 | mtsBatchList[1] = mtsBatchList[2] - mtsBatchList[1] 454 | mtsBatchList[2] = mtsBatchList[3] - mtsBatchList[2] 455 | mtsBatchList[3] = time.Now().UnixMilli() - mtsBatchList[3] 456 | return mtsBatchList[:], nil 457 | } 458 | 459 | //////////////////////////////// 460 | func RollbackOpStateBatch(rollback DataRollbackType) (int64, error) { 461 | mtss := time.Now().UnixMilli() 462 | txRocks, _, err := SaveStateBatchRocksBegin(rollback.StateMapBefore, nil) 463 | defer txRocks.Destroy() 464 | if err != nil { 465 | return 0, err 466 | } 467 | _, err = SaveStateBatchCassa(rollback.StateMapBefore) 468 | if err != nil { 469 | txRocks.Rollback() 470 | return 0, err 471 | } 472 | _, err = DeleteOpDataBatchCassa(rollback.OpScoreList, rollback.TxIdList) 473 | if err != nil { 474 | txRocks.Rollback() 475 | return 0, err 476 | } 477 | err = txRocks.Commit() 478 | if err != nil { 479 | txRocks.Rollback() 480 | return 0, err 481 | } 482 | return time.Now().UnixMilli() - mtss, nil 483 | } 484 | -------------------------------------------------------------------------------- /storage/type.go: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////// 3 | package storage 4 | 5 | import ( 6 | "kasplex-executor/protowire" 7 | ) 8 | 9 | //////////////////////////////// 10 | type DataVspcType struct { 11 | DaaScore uint64 `json:"daaScore"` 12 | Hash string `json:"hash"` 13 | TxIdList []string `json:"-"` 14 | } 15 | 16 | //////////////////////////////// 17 | type DataTransactionType struct { 18 | TxId string 19 | DaaScore uint64 20 | BlockAccept string 21 | Data *protowire.RpcTransaction 22 | } 23 | 24 | //////////////////////////////// 25 | type DataScriptType struct { 26 | P string `json:"p"` 27 | Op string `json:"op"` 28 | From string `json:"from,omitempty"` 29 | To string `json:"to,omitempty"` 30 | Tick string `json:"tick,omitempty"` 31 | Max string `json:"max,omitempty"` 32 | Lim string `json:"lim,omitempty"` 33 | Pre string `json:"pre,omitempty"` 34 | Dec string `json:"dec,omitempty"` 35 | Amt string `json:"amt,omitempty"` 36 | Utxo string `json:"utxo,omitempty"` 37 | Price string `json:"price,omitempty"` 38 | Mod string `json:"mod,omitempty"` 39 | Name string `json:"name,omitempty"` 40 | Ca string `json:"ca,omitempty"` 41 | // ... 42 | } 43 | 44 | //////////////////////////////// 45 | type DataOpStateType struct { 46 | BlockAccept string `json:"blockaccept,omitempty"` 47 | Fee uint64 `json:"fee,omitempty"` 48 | FeeLeast uint64 `json:"feeleast,omitempty"` 49 | MtsAdd int64 `json:"mtsadd,omitempty"` 50 | OpScore uint64 `json:"opscore,omitempty"` 51 | OpAccept int8 `json:"opaccept,omitempty"` 52 | OpError string `json:"operror,omitempty"` 53 | Checkpoint string `json:"checkpoint,omitempty"` 54 | } 55 | 56 | //////////////////////////////// 57 | type DataStatsType struct { 58 | TickAffc []string 59 | AddressAffc []string 60 | // XxxAffc ... 61 | } 62 | 63 | //////////////////////////////// 64 | type DataOperationType struct { 65 | TxId string 66 | DaaScore uint64 67 | BlockAccept string 68 | Fee uint64 69 | FeeLeast uint64 70 | MtsAdd int64 71 | OpScore uint64 72 | OpAccept int8 73 | OpError string 74 | OpScript []*DataScriptType 75 | ScriptSig string 76 | StBefore []string 77 | StAfter []string 78 | Checkpoint string 79 | SsInfo *DataStatsType 80 | } 81 | 82 | //////////////////////////////// 83 | type StateTokenMetaType struct { 84 | Max string `json:"max,omitempty"` 85 | Lim string `json:"lim,omitempty"` 86 | Pre string `json:"pre,omitempty"` 87 | Dec int `json:"dec,omitempty"` 88 | Mod string `json:"mod,omitempty"` 89 | From string `json:"from,omitempty"` 90 | To string `json:"to,omitempty"` 91 | Name string `json:"name,omitempty"` 92 | TxId string `json:"txid,omitempty"` 93 | OpAdd uint64 `json:"opadd,omitempty"` 94 | MtsAdd int64 `json:"mtsadd,omitempty"` 95 | } 96 | 97 | //////////////////////////////// 98 | type StateTokenType struct { 99 | Tick string `json:"tick,omitempty"` 100 | Max string `json:"max,omitempty"` 101 | Lim string `json:"lim,omitempty"` 102 | Pre string `json:"pre,omitempty"` 103 | Dec int `json:"dec,omitempty"` 104 | Mod string `json:"mod,omitempty"` 105 | From string `json:"from,omitempty"` 106 | To string `json:"to,omitempty"` 107 | Minted string `json:"minted,omitempty"` 108 | Burned string `json:"burned,omitempty"` 109 | Name string `json:"name,omitempty"` 110 | TxId string `json:"txid,omitempty"` 111 | OpAdd uint64 `json:"opadd,omitempty"` 112 | OpMod uint64 `json:"opmod,omitempty"` 113 | MtsAdd int64 `json:"mtsadd,omitempty"` 114 | MtsMod int64 `json:"mtsmod,omitempty"` 115 | } 116 | 117 | //////////////////////////////// 118 | type StateBalanceType struct { 119 | Address string `json:"address,omitempty"` 120 | Tick string `json:"tick,omitempty"` 121 | Dec int `json:"dec,omitempty"` 122 | Balance string `json:"balance,omitempty"` 123 | Locked string `json:"locked,omitempty"` 124 | OpMod uint64 `json:"opmod,omitempty"` 125 | } 126 | 127 | //////////////////////////////// 128 | type StateMarketType struct { 129 | Tick string `json:"tick,omitempty"` 130 | TAddr string `json:"taddr,omitempty"` 131 | UTxId string `json:"utxid,omitempty"` 132 | UAddr string `json:"uaddr,omitempty"` 133 | UAmt string `json:"uamt,omitempty"` 134 | UScript string `json:"uscript,omitempty"` 135 | TAmt string `json:"tamt,omitempty"` 136 | OpAdd uint64 `json:"opadd,omitempty"` 137 | } 138 | 139 | //////////////////////////////// 140 | type StateBlacklistType struct { 141 | Tick string `json:"tick,omitempty"` 142 | Address string `json:"address,omitempty"` 143 | OpAdd uint64 `json:"opadd,omitempty"` 144 | } 145 | 146 | //////////////////////////////// 147 | // type StateXxx ... 148 | 149 | //////////////////////////////// 150 | type DataStateMapType struct { 151 | StateTokenMap map[string]*StateTokenType `json:"statetokenmap,omitempty"` 152 | StateBalanceMap map[string]*StateBalanceType `json:"statebalancemap,omitempty"` 153 | StateMarketMap map[string]*StateMarketType `json:"statemarketmap,omitempty"` 154 | StateBlacklistMap map[string]*StateBlacklistType `json:"stateblacklistmap,omitempty"` 155 | // StateXxx ... 156 | } 157 | 158 | //////////////////////////////// 159 | type DataRollbackType struct { 160 | DaaScoreStart uint64 `json:"daascorestart"` 161 | DaaScoreEnd uint64 `json:"daascoreend"` 162 | CheckpointBefore string `json:"checkpointbefore"` 163 | CheckpointAfter string `json:"checkpointafter"` 164 | OpScoreLast uint64 `json:"opscorelast"` 165 | StateMapBefore DataStateMapType `json:"statemapbefore"` 166 | OpScoreList []uint64 `json:"opscorelist"` 167 | TxIdList []string `json:"txidlist"` 168 | } 169 | 170 | //////////////////////////////// 171 | type DataInputType struct { 172 | Hash string 173 | Index uint 174 | Amount uint64 175 | } 176 | 177 | //////////////////////////////// 178 | type DataFeeType struct { 179 | Txid string 180 | InputList []DataInputType 181 | AmountOut uint64 182 | Fee uint64 183 | } 184 | 185 | // ... 186 | --------------------------------------------------------------------------------