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  }