github.com/0chain/gosdk@v1.17.11/core/transaction/entity.go (about) 1 // Provides low-level functions and types to work with the native smart contract transactions. 2 package transaction 3 4 import ( 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "strings" 9 "sync" 10 "time" 11 12 "github.com/0chain/errors" 13 "github.com/0chain/gosdk/core/common" 14 "github.com/0chain/gosdk/core/encryption" 15 "github.com/0chain/gosdk/core/util" 16 lru "github.com/hashicorp/golang-lru" 17 ) 18 19 const TXN_SUBMIT_URL = "v1/transaction/put" 20 const TXN_VERIFY_URL = "v1/transaction/get/confirmation?hash=" 21 const BLOCK_BY_ROUND_URL = "v1/screst/6dba10422e368813802877a85039d3985d96760ed844092319743fb3a76712d7/block?round=" 22 23 const ( 24 TxnSuccess = 1 // Indicates the transaction is successful in updating the state or smart contract 25 TxnChargeableError = 2 // Indicates the transaction is successful in updating the state or smart contract 26 TxnFail = 3 // Indicates a transaction has failed to update the state or smart contract 27 ) 28 29 // TxnReceipt - a transaction receipt is a processed transaction that contains the output 30 type TxnReceipt struct { 31 Transaction *Transaction 32 } 33 34 // SmartContractTxnData data structure to hold the smart contract transaction data 35 type SmartContractTxnData struct { 36 Name string `json:"name"` 37 InputArgs interface{} `json:"input"` 38 } 39 40 type StorageAllocation struct { 41 ID string `json:"id"` 42 DataShards int `json:"data_shards"` 43 ParityShards int `json:"parity_shards"` 44 Size int64 `json:"size"` 45 Expiration int64 `json:"expiration_date"` 46 Owner string `json:"owner_id"` 47 OwnerPublicKey string `json:"owner_public_key"` 48 ReadRatio *Ratio `json:"read_ratio"` 49 WriteRatio *Ratio `json:"write_ratio"` 50 MinLockDemand float64 `json:"min_lock_demand"` 51 } 52 type Ratio struct { 53 ZCN int64 `json:"zcn"` 54 Size int64 `json:"size"` 55 } 56 type RoundBlockHeader struct { 57 Version string `json:"version"` 58 CreationDate int64 `json:"creation_date"` 59 Hash string `json:"block_hash"` 60 PreviousBlockHash string `json:"previous_block_hash"` 61 MinerID string `json:"miner_id"` 62 Round int64 `json:"round"` 63 RoundRandomSeed int64 `json:"round_random_seed"` 64 MerkleTreeRoot string `json:"merkle_tree_root"` 65 StateChangesCount int `json:"state_changes_count"` 66 StateHash string `json:"state_hash"` 67 ReceiptMerkleTreeRoot string `json:"receipt_merkle_tree_root"` 68 NumberOfTxns int64 `json:"num_txns"` 69 } 70 71 type Block struct { 72 Hash string `json:"hash" gorm:"uniqueIndex:idx_bhash"` 73 Version string `json:"version"` 74 CreationDate int64 `json:"creation_date" gorm:"index:idx_bcreation_date"` 75 Round int64 `json:"round" gorm:"index:idx_bround"` 76 MinerID string `json:"miner_id"` 77 RoundRandomSeed int64 `json:"round_random_seed"` 78 MerkleTreeRoot string `json:"merkle_tree_root"` 79 StateHash string `json:"state_hash"` 80 ReceiptMerkleTreeRoot string `json:"receipt_merkle_tree_root"` 81 NumTxns int `json:"num_txns"` 82 MagicBlockHash string `json:"magic_block_hash"` 83 PrevHash string `json:"prev_hash"` 84 Signature string `json:"signature"` 85 ChainId string `json:"chain_id"` 86 StateChangesCount int `json:"state_changes_count"` 87 RunningTxnCount string `json:"running_txn_count"` 88 RoundTimeoutCount int `json:"round_timeout_count"` 89 } 90 91 const ( 92 NEW_ALLOCATION_REQUEST = "new_allocation_request" 93 NEW_FREE_ALLOCATION = "free_allocation_request" 94 UPDATE_ALLOCATION_REQUEST = "update_allocation_request" 95 LOCK_TOKEN = "lock" 96 UNLOCK_TOKEN = "unlock" 97 98 ADD_FREE_ALLOCATION_ASSIGNER = "add_free_storage_assigner" 99 100 // Vesting SC 101 VESTING_TRIGGER = "trigger" 102 VESTING_STOP = "stop" 103 VESTING_UNLOCK = "unlock" 104 VESTING_ADD = "add" 105 VESTING_DELETE = "delete" 106 VESTING_UPDATE_SETTINGS = "vestingsc-update-settings" 107 108 // Storage SC 109 STORAGESC_FINALIZE_ALLOCATION = "finalize_allocation" 110 STORAGESC_CANCEL_ALLOCATION = "cancel_allocation" 111 STORAGESC_CREATE_ALLOCATION = "new_allocation_request" 112 STORAGESC_CREATE_READ_POOL = "new_read_pool" 113 STORAGESC_READ_POOL_LOCK = "read_pool_lock" 114 STORAGESC_READ_POOL_UNLOCK = "read_pool_unlock" 115 STORAGESC_STAKE_POOL_LOCK = "stake_pool_lock" 116 STORAGESC_STAKE_POOL_UNLOCK = "stake_pool_unlock" 117 STORAGESC_UPDATE_BLOBBER_SETTINGS = "update_blobber_settings" 118 STORAGESC_UPDATE_VALIDATOR_SETTINGS = "update_validator_settings" 119 STORAGESC_UPDATE_ALLOCATION = "update_allocation_request" 120 STORAGESC_WRITE_POOL_LOCK = "write_pool_lock" 121 STORAGESC_WRITE_POOL_UNLOCK = "write_pool_unlock" 122 STORAGESC_UPDATE_SETTINGS = "update_settings" 123 ADD_HARDFORK = "add_hardfork" 124 STORAGESC_COLLECT_REWARD = "collect_reward" 125 STORAGESC_KILL_BLOBBER = "kill_blobber" 126 STORAGESC_KILL_VALIDATOR = "kill_validator" 127 STORAGESC_SHUTDOWN_BLOBBER = "shutdown_blobber" 128 STORAGESC_SHUTDOWN_VALIDATOR = "shutdown_validator" 129 STORAGESC_RESET_BLOBBER_STATS = "reset_blobber_stats" 130 STORAGESC_RESET_ALLOCATION_STATS = "reset_allocation_stats" 131 132 MINERSC_LOCK = "addToDelegatePool" 133 MINERSC_UNLOCK = "deleteFromDelegatePool" 134 MINERSC_MINER_SETTINGS = "update_miner_settings" 135 MINERSC_SHARDER_SETTINGS = "update_sharder_settings" 136 MINERSC_UPDATE_SETTINGS = "update_settings" 137 MINERSC_UPDATE_GLOBALS = "update_globals" 138 MINERSC_MINER_DELETE = "delete_miner" 139 MINERSC_SHARDER_DELETE = "delete_sharder" 140 MINERSC_COLLECT_REWARD = "collect_reward" 141 MINERSC_KILL_MINER = "kill_miner" 142 MINERSC_KILL_SHARDER = "kill_sharder" 143 144 // Faucet SC 145 FAUCETSC_UPDATE_SETTINGS = "update-settings" 146 147 // ZCNSC smart contract 148 149 ZCNSC_UPDATE_GLOBAL_CONFIG = "update-global-config" 150 ZCNSC_UPDATE_AUTHORIZER_CONFIG = "update-authorizer-config" 151 ZCNSC_ADD_AUTHORIZER = "add-authorizer" 152 ZCNSC_AUTHORIZER_HEALTH_CHECK = "authorizer-health-check" 153 ZCNSC_DELETE_AUTHORIZER = "delete-authorizer" 154 ZCNSC_COLLECT_REWARD = "collect-rewards" 155 ZCNSC_LOCK = "add-to-delegate-pool" 156 ZCNSC_UNLOCK = "delete-from-delegate-pool" 157 158 ESTIMATE_TRANSACTION_COST = `/v1/estimate_txn_fee` 159 FEES_TABLE = `/v1/fees_table` 160 ) 161 162 type SignFunc = func(msg string) (string, error) 163 type VerifyFunc = func(publicKey, signature, msgHash string) (bool, error) 164 type SignWithWallet = func(msg string, wallet interface{}) (string, error) 165 166 var cache *lru.Cache 167 168 func init() { 169 var err error 170 cache, err = lru.New(100) 171 if err != nil { 172 fmt.Println("caching Initilization failed, err:", err) 173 } 174 } 175 176 func NewTransactionEntity(clientID string, chainID string, publicKey string, nonce int64) *Transaction { 177 txn := &Transaction{} 178 txn.Version = "1.0" 179 txn.ClientID = clientID 180 txn.CreationDate = int64(common.Now()) 181 txn.ChainID = chainID 182 txn.PublicKey = publicKey 183 txn.TransactionNonce = nonce 184 return txn 185 } 186 187 func (t *Transaction) ComputeHashAndSignWithWallet(signHandler SignWithWallet, signingWallet interface{}) error { 188 t.ComputeHashData() 189 var err error 190 t.Signature, err = signHandler(t.Hash, signingWallet) 191 if err != nil { 192 return err 193 } 194 return nil 195 } 196 197 func (t *Transaction) ComputeHashAndSign(signHandler SignFunc) error { 198 t.ComputeHashData() 199 var err error 200 t.Signature, err = signHandler(t.Hash) 201 if err != nil { 202 return err 203 } 204 return nil 205 } 206 207 func (t *Transaction) ComputeHashData() { 208 hashdata := fmt.Sprintf("%v:%v:%v:%v:%v:%v", t.CreationDate, t.TransactionNonce, t.ClientID, 209 t.ToClientID, t.Value, encryption.Hash(t.TransactionData)) 210 t.Hash = encryption.Hash(hashdata) 211 } 212 213 func (t *Transaction) DebugJSON() []byte { 214 jsonByte, err := json.MarshalIndent(t, "", " ") 215 if err != nil { 216 panic(err) // This JSONify function only supposed to be debug-only anyway. 217 } 218 return jsonByte 219 } 220 221 // GetHash - implement interface 222 func (rh *TxnReceipt) GetHash() string { 223 return rh.Transaction.OutputHash 224 } 225 226 /*GetHashBytes - implement Hashable interface */ 227 func (rh *TxnReceipt) GetHashBytes() []byte { 228 return util.HashStringToBytes(rh.Transaction.OutputHash) 229 } 230 231 // NewTransactionReceipt - create a new transaction receipt 232 func NewTransactionReceipt(t *Transaction) *TxnReceipt { 233 return &TxnReceipt{Transaction: t} 234 } 235 236 // VerifySigWith verify the signature with the given public key and handler 237 func (t *Transaction) VerifySigWith(pubkey string, verifyHandler VerifyFunc) (bool, error) { 238 // Store the hash 239 hash := t.Hash 240 t.ComputeHashData() 241 if t.Hash != hash { 242 return false, errors.New("verify_transaction", fmt.Sprintf(`{"error":"hash_mismatch", "expected":"%v", "actual":%v"}`, t.Hash, hash)) 243 } 244 return verifyHandler(pubkey, t.Signature, t.Hash) 245 } 246 247 func SendTransactionSync(txn *Transaction, miners []string) error { 248 wg := sync.WaitGroup{} 249 wg.Add(len(miners)) 250 fails := make(chan error, len(miners)) 251 252 for _, miner := range miners { 253 url := fmt.Sprintf("%v/%v", miner, TXN_SUBMIT_URL) 254 go func() { 255 _, err := sendTransactionToURL(url, txn, &wg) 256 if err != nil { 257 fails <- err 258 } 259 wg.Done() 260 }() //nolint 261 } 262 wg.Wait() 263 close(fails) 264 265 failureCount := 0 266 messages := make(map[string]int) 267 for e := range fails { 268 if e != nil { 269 failureCount++ 270 messages[e.Error()] += 1 271 } 272 } 273 274 max := 0 275 dominant := "" 276 for m, s := range messages { 277 if s > max { 278 dominant = m 279 } 280 } 281 282 if failureCount == len(miners) { 283 return errors.New("transaction_send_error", dominant) 284 } 285 286 return nil 287 } 288 289 func sendTransactionToURL(url string, txn *Transaction, wg *sync.WaitGroup) ([]byte, error) { 290 postReq, err := util.NewHTTPPostRequest(url, txn) 291 if err != nil { 292 //Logger.Error("Error in serializing the transaction", txn, err.Error()) 293 return nil, err 294 } 295 postResponse, err := postReq.Post() 296 if postResponse.StatusCode >= 200 && postResponse.StatusCode <= 299 { 297 return []byte(postResponse.Body), nil 298 } 299 return nil, errors.Wrap(err, errors.New("transaction_send_error", postResponse.Body)) 300 } 301 302 type cachedObject struct { 303 Expiration time.Duration 304 Value interface{} 305 } 306 307 func retriveFromTable(table map[string]map[string]int64, txnName, toAddress string) (uint64, error) { 308 var fees uint64 309 if val, ok := table[toAddress]; ok { 310 fees = uint64(val[txnName]) 311 } else { 312 if txnName == "transfer" { 313 fees = uint64(table["transfer"]["transfer"]) 314 } else { 315 return 0, fmt.Errorf("invalid transaction") 316 } 317 } 318 return fees, nil 319 } 320 321 // EstimateFee estimates transaction fee 322 func EstimateFee(txn *Transaction, miners []string, reqPercent ...float32) (uint64, error) { 323 const minReqNum = 3 324 var reqN int 325 326 if len(reqPercent) > 0 { 327 reqN = int(reqPercent[0] * float32(len(miners))) 328 } 329 330 txData := txn.TransactionData 331 332 var sn SmartContractTxnData 333 err := json.Unmarshal([]byte(txData), &sn) 334 if err != nil { 335 return 0, err 336 } 337 338 txnName := sn.Name 339 txnName = strings.ToLower(txnName) 340 toAddress := txn.ToClientID 341 342 reqN = util.MaxInt(minReqNum, reqN) 343 reqN = util.MinInt(reqN, len(miners)) 344 randomMiners := util.Shuffle(miners)[:reqN] 345 346 // Retrieve the object from the cache 347 cached, ok := cache.Get(FEES_TABLE) 348 if ok { 349 cachedObj, ok := cached.(*cachedObject) 350 if ok { 351 table := cachedObj.Value.(map[string]map[string]int64) 352 fees, err := retriveFromTable(table, txnName, toAddress) 353 if err != nil { 354 return 0, err 355 } 356 return fees, nil 357 } 358 } 359 360 table, err := GetFeesTable(randomMiners, reqPercent...) 361 if err != nil { 362 return 0, err 363 } 364 365 fees, err := retriveFromTable(table, txnName, toAddress) 366 if err != nil { 367 return 0, err 368 } 369 370 cache.Add(FEES_TABLE, &cachedObject{ 371 Expiration: 30 * time.Hour, 372 Value: table, 373 }) 374 375 return fees, nil 376 } 377 378 // GetFeesTable get fee tables 379 func GetFeesTable(miners []string, reqPercent ...float32) (map[string]map[string]int64, error) { 380 const minReqNum = 3 381 var reqN int 382 383 if len(reqPercent) > 0 { 384 reqN = int(reqPercent[0] * float32(len(miners))) 385 } 386 387 reqN = util.MaxInt(minReqNum, reqN) 388 reqN = util.MinInt(reqN, len(miners)) 389 randomMiners := util.Shuffle(miners)[:reqN] 390 391 var ( 392 feesC = make(chan string, reqN) 393 errC = make(chan error, reqN) 394 ) 395 396 wg := &sync.WaitGroup{} 397 wg.Add(len(randomMiners)) 398 399 for _, miner := range randomMiners { 400 go func(minerUrl string) { 401 defer wg.Done() 402 403 url := minerUrl + FEES_TABLE 404 req, err := util.NewHTTPGetRequest(url) 405 if err != nil { 406 errC <- fmt.Errorf("create request failed, url: %s, err: %v", url, err) 407 return 408 } 409 410 res, err := req.Get() 411 if err != nil { 412 errC <- fmt.Errorf("request failed, url: %s, err: %v", url, err) 413 return 414 } 415 416 if res.StatusCode == http.StatusOK { 417 feesC <- res.Body 418 return 419 } 420 421 feesC <- "" 422 423 }(miner) 424 } 425 426 // wait for requests to complete 427 wg.Wait() 428 close(feesC) 429 close(errC) 430 431 feesCount := make(map[string]int, reqN) 432 for f := range feesC { 433 feesCount[f]++ 434 } 435 436 if len(feesCount) > 0 { 437 var ( 438 max int 439 fees string 440 ) 441 442 for f, count := range feesCount { 443 if f != "" && count > max { 444 max = count 445 fees = f 446 } 447 } 448 449 feesTable := make(map[string]map[string]int64) 450 err := json.Unmarshal([]byte(fees), &feesTable) 451 if err != nil { 452 return nil, errors.New("failed to get fees table", err.Error()) 453 } 454 455 return feesTable, nil 456 } 457 458 errs := make([]string, 0, reqN) 459 for err := range errC { 460 errs = append(errs, err.Error()) 461 } 462 463 return nil, errors.New("failed to get fees table", strings.Join(errs, ",")) 464 }