Advertisement
den4ik2003

Untitled

Nov 8th, 2024
107
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Go 12.69 KB | None | 0 0
  1. package main
  2.  
  3. import "C"
  4.  
  5. import (
  6.     "bytes"
  7.     "context"
  8.     "crypto/ecdsa"
  9.     "encoding/json"
  10.     "fmt"
  11.     "io"
  12.     "log"
  13.     "os"
  14.     "strconv"
  15.     "strings"
  16.     "sync"
  17.     "sync/atomic"
  18.     "time"
  19.     "unsafe"
  20.  
  21.     "github.com/ethereum/go-ethereum"
  22.     "github.com/ethereum/go-ethereum/accounts/abi"
  23.     "github.com/ethereum/go-ethereum/accounts/abi/bind"
  24.     "github.com/ethereum/go-ethereum/common"
  25.     "github.com/ethereum/go-ethereum/common/hexutil"
  26.     "github.com/ethereum/go-ethereum/core/types"
  27.     "github.com/ethereum/go-ethereum/crypto"
  28.     "github.com/ethereum/go-ethereum/ethclient"
  29. )
  30.  
  31. type Priority int
  32.  
  33. const (
  34.     DEFAULT Priority = iota
  35.     HIGH
  36. )
  37.  
  38. type TxInfo struct {
  39.     submitTime time.Time
  40.     timeout    time.Duration
  41.     priority   Priority
  42.     nonce      int64
  43.     hash       string
  44. }
  45.  
  46. type Client struct {
  47.     publicKey         *ecdsa.PublicKey
  48.     privateKey        *ecdsa.PrivateKey
  49.     sender            common.Address
  50.     innerClient       *ethclient.Client
  51.     nonce             uint64
  52.     processingRetries int64
  53.     contracts         map[string]Contract // TODO: use self-contract in order to eliminate from expensive function finding
  54.     muCancelOldTx     sync.Mutex
  55.     waitPrevFinishing chan struct{}
  56. }
  57.  
  58. type Contract struct {
  59.     boundContract *bind.BoundContract
  60.     abi           abi.ABI
  61. }
  62.  
  63. var (
  64.     globalClient *Client
  65. )
  66.  
  67. //export Init
  68. func Init(nodeKey, privateKey *C.char) uintptr {
  69.     innerClient, err := ethclient.Dial("https://polygon-mainnet.infura.io/v3/" + C.GoString(nodeKey))
  70.     if err != nil {
  71.         log.Fatalf("failed to create client: %v", err)
  72.     }
  73.  
  74.     privateKeyECDSA, err := crypto.HexToECDSA(C.GoString(privateKey)[2:])
  75.     if err != nil {
  76.         log.Fatalf("failed to decode private key: %v", err)
  77.     }
  78.     publicKey := privateKeyECDSA.Public()
  79.     publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
  80.     if !ok {
  81.         log.Fatalf("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
  82.     }
  83.     address := crypto.PubkeyToAddress(*publicKeyECDSA)
  84.     initNonce, err := innerClient.NonceAt(context.Background(), address, nil)
  85.     // initNonce, err := innerClient.PendingNonceAt(context.Background(), address)
  86.     log.Printf("init Nonce: %d\n", initNonce)
  87.     if err != nil {
  88.         log.Fatalf("cannot get nonce: PendingNonceAt function failed: %v", err)
  89.     }
  90.  
  91.     globalClient = &Client{ // клиент снимется с кучи? (не сразу же даже если так, а в следующую итерацию GC)
  92.         publicKey:         publicKeyECDSA,
  93.         privateKey:        privateKeyECDSA,
  94.         sender:            address,
  95.         innerClient:       innerClient,
  96.         nonce:             uint64(initNonce),
  97.         contracts:         make(map[string]Contract, 16), // 16 это примерная оценка
  98.         waitPrevFinishing: make(chan struct{}, 1),
  99.     }
  100.     return uintptr(unsafe.Pointer(globalClient)) // no dangling reference?
  101. }
  102.  
  103. //export GetWalletAddress
  104. func GetWalletAddress(clientPtr unsafe.Pointer) *C.char {
  105.     client := (*Client)(clientPtr)
  106.     if client == nil {
  107.         log.Fatal("no client")
  108.     }
  109.     return C.CString(client.sender.String())
  110. }
  111.  
  112. //export SignMessage
  113. func SignMessage(clientPtr unsafe.Pointer, message *C.char) *C.char {
  114.     client := (*Client)(clientPtr)
  115.     if client == nil {
  116.         log.Fatal("no client")
  117.     }
  118.     data := []byte(C.GoString(message))
  119.     hash := crypto.Keccak256Hash(data)
  120.     signature, err := crypto.Sign(hash.Bytes(), client.privateKey)
  121.     if err != nil {
  122.         log.Fatal(err) // пока пусть, тк юзаем всего 1 раз при инициализации
  123.     }
  124.  
  125.     return C.CString(hexutil.Encode(signature))
  126. }
  127.  
  128. //export BindSmartContract
  129. func BindSmartContract(clientPtr unsafe.Pointer, contractAddress, pathToAbi *C.char) {
  130.     lowerContractAddress := strings.ToLower(C.GoString(contractAddress))
  131.     client := (*Client)(clientPtr)
  132.     if client == nil {
  133.         log.Fatal("no client")
  134.     }
  135.  
  136.     file, err := os.Open(C.GoString(pathToAbi))
  137.     if err != nil {
  138.         log.Fatalf("failed to open ABI file: %v", err)
  139.     }
  140.     defer file.Close()
  141.  
  142.     content, err := io.ReadAll(file)
  143.     if err != nil {
  144.         log.Fatalf("failed to read ABI file: %v", err)
  145.     }
  146.  
  147.     var contractInfo map[string]interface{}
  148.     if json.Unmarshal(content, &contractInfo); err != nil {
  149.         log.Fatalf("failed to unmarshal ABI JSON: %v", err)
  150.     }
  151.  
  152.     contractABI, err := json.Marshal(contractInfo["abi"])
  153.     if err != nil {
  154.         log.Fatalf("failed to marshal ABI: %v", err)
  155.     }
  156.  
  157.     parsedABI, err := abi.JSON(bytes.NewReader(contractABI))
  158.     if err != nil {
  159.         log.Fatalf("failed to parse ABI: %v", err)
  160.     }
  161.  
  162.     address := common.HexToAddress(lowerContractAddress)
  163.     contract := bind.NewBoundContract(address, parsedABI, client.innerClient, client.innerClient, client.innerClient)
  164.  
  165.     client.contracts[lowerContractAddress] = Contract{boundContract: contract, abi: parsedABI}
  166.  
  167.     log.Printf("bind contract %s", lowerContractAddress)
  168. }
  169.  
  170. //export ContractCallConstFunc
  171. func ContractCallConstFunc(clientPtr unsafe.Pointer, contractAddress, cFunctionName, args *C.char) *C.char {
  172.     lowerContractAddress := strings.ToLower(C.GoString(contractAddress))
  173.     client := (*Client)(clientPtr)
  174.     if client == nil {
  175.         log.Fatal("no client")
  176.     }
  177.     contract, found := client.contracts[lowerContractAddress]
  178.     if !found {
  179.         log.Printf("No contract %s", lowerContractAddress)
  180.         return C.CString("{\"error\":\"no contract\"}")
  181.     }
  182.     functionName := C.GoString(cFunctionName)
  183.     functionAbi, found := contract.abi.Methods[functionName]
  184.     if !found {
  185.         log.Println("No function in abi")
  186.         return C.CString("{\"error\":\"no function in abi\"}")
  187.     }
  188.  
  189.     var callInput []interface{}
  190.     inputStrToFunctionCallParams(functionAbi, C.GoString(args), &callInput)
  191.  
  192.     callOpts := &bind.CallOpts{
  193.         Context: context.Background(),
  194.     }
  195.     var output []interface{}
  196.     err := contract.boundContract.Call(callOpts, &output, functionName, callInput...) // в горутине запускать мб? а как горутины в cgo работают?
  197.  
  198.     if err != nil {
  199.         return C.CString(fmt.Sprintf("{\"error\":\"err in call function: %v\"}", err))
  200.     }
  201.  
  202.     resultOut, err := formOutput(functionAbi, &output)
  203.     if err != nil {
  204.         return C.CString(fmt.Sprintf("{\"error\":\"formOutput error: %v\"}", err))
  205.     }
  206.     return C.CString(resultOut)
  207. }
  208.  
  209. //export ContractCallMutFunc
  210. func ContractCallMutFunc(clientPtr unsafe.Pointer, contractAddress, cFunctionName, args, submitExtends *C.char) *C.char {
  211.     log.Printf("extends args: %s", C.GoString(submitExtends))
  212.     log.Printf("args: %s", C.GoString(args))
  213.  
  214.     client := (*Client)(clientPtr)
  215.     if client == nil {
  216.         log.Fatal("no client")
  217.     }
  218.     lowerContractAddress := strings.ToLower(C.GoString(contractAddress))
  219.     contract, found := client.contracts[lowerContractAddress]
  220.     if !found {
  221.         log.Printf("No contract %s", lowerContractAddress)
  222.         return C.CString("{\"error\":\"no contract\", \"tx_hash\":\"0\"}")
  223.     }
  224.     functionName := C.GoString(cFunctionName)
  225.     functionAbi, found := contract.abi.Methods[functionName]
  226.     if !found {
  227.         log.Println("No function in abi")
  228.         return C.CString("{\"error\":\"no function in abi\", \"tx_hash\":\"0\"}")
  229.     }
  230.  
  231.     // client.waitPrevFinishing <- struct{}{} // чтобы последовательно транзакции делали
  232.     // defer func() {
  233.     //  <-client.waitPrevFinishing
  234.     // }()
  235.  
  236.     if atomic.LoadInt64(&client.processingRetries) != 0 { // чтобы не скапливались транзакции
  237.         return C.CString("{\"error\":\"any tx already being processed\", \"tx_hash\":\"0\"}")
  238.     }
  239.  
  240.     var submitExtendsMap map[string]interface{}
  241.     err := json.Unmarshal([]byte(C.GoString(submitExtends)), &submitExtendsMap)
  242.     if err != nil {
  243.         log.Printf("{\"error\":\"can't parse extends info: %v\", \"tx_hash\":\"0\"}", err)
  244.         return C.CString(
  245.             fmt.Sprintf("{\"error\":\"can't parse extends info: %v\", \"tx_hash\":\"0\"}", err),
  246.         )
  247.     }
  248.     if _, exists := submitExtendsMap["priority"]; !exists {
  249.         submitExtendsMap["priority"] = "0"
  250.     }
  251.     if _, exists := submitExtendsMap["timeout"]; !exists {
  252.         submitExtendsMap["timeout"] = "300000" // 5 min
  253.     }
  254.     var txPriority Priority
  255.     if submitExtendsMap["priority"].(string) == "0" {
  256.         txPriority = DEFAULT
  257.     } else {
  258.         log.Println("High priority")
  259.         txPriority = HIGH
  260.     }
  261.     timeout, err := strconv.Atoi(submitExtendsMap["timeout"].(string))
  262.     if err != nil {
  263.         log.Fatalf("can't convert timeout to int: %v", err)
  264.     }
  265.     newNonce := atomic.AddUint64(&client.nonce, 1) - 1 // innerClient.NonceAt(context.Background(), address, nil)
  266.  
  267.     auth, err := FormTransactor(client, newNonce, 3000000, txPriority)
  268.     if err != nil {
  269.         log.Fatalf("formTransactor() error: %v", err)
  270.     }
  271.     var callInput []interface{}
  272.     inputStrToFunctionCallParams(functionAbi, C.GoString(args), &callInput)
  273.  
  274.     tx, err := contract.boundContract.Transact(auth, functionName, callInput...) // first try
  275.     if err != nil {
  276.         log.Printf("Failed to call contract function: %v", err)
  277.         return C.CString(
  278.             "{\"error\":\"failed to call contarct function\", \"tx_hash\":\"0\"}",
  279.         )
  280.     }
  281.     firstTxHash := tx.Hash().Hex()
  282.     log.Printf("TX Hash: %s ; Nonce: %d\n", tx.Hash().Hex(), newNonce)
  283.  
  284.     var receipt *types.Receipt
  285.     retries := 0
  286.     var submitTime time.Time
  287.     retryMultiplicators := []float64{2.0, 2.5, 3.0, 3.0}
  288.     for {
  289.         submitTime = time.Now()
  290.         ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Millisecond)
  291.         defer cancel()
  292.         receipt, err = bind.WaitMined(ctx, client.innerClient, tx)
  293.         if err != nil { // timeout exceeded -> cancel
  294.             retries++
  295.             timeout = 12000   // 12000 ms = 12 s: Пока что это было выбрано по маленькому массиву данных
  296.             if retries == 1 { // будет первый ретрай
  297.                 atomic.AddInt64(&client.processingRetries, 1)
  298.             }
  299.             log.Printf("timeout_metric -> exceeded: tx: %s ; timeout %d ms ; first tx: %s", tx.Hash().Hex(), timeout, firstTxHash)
  300.             if retries > 3 { // отправляем финальную ждать
  301.                 if retries == 5 { // то есть экстра-ретрай 5 минут тоже не помог
  302.                     log.Fatalf("Extra retry was exceeded for first tx: %s", firstTxHash)
  303.                 }
  304.                 // не падаем, тк если ночь, то не хочется класть бота, вдруг через 5 минут заретраится
  305.                 log.Printf("Warning: call extra retry for first tx: %s", firstTxHash) // TODO вынести в метрики чтобы можно было увед получить
  306.                 timeout = 300000                                                      // 5 min: TODO: параметром задать предельное время ожидания
  307.             }
  308.             cancelTx, err := CancelTx(client, tx.Hash().Hex(), newNonce, retryMultiplicators[retries-1])
  309.             if err != nil {
  310.                 return C.CString(
  311.                     fmt.Sprintf(
  312.                         "{\"error\":\"don't know: %v\", \"tx_hash\":\"%s\"}",
  313.                         err, firstTxHash,
  314.                     ),
  315.                 )
  316.             }
  317.             tx = cancelTx
  318.         } else { // succeed
  319.             log.Printf("timeout_metric -> complete: tx: %s ; exec_time: %fs ; attempt = %d ; first tx %s\n", tx.Hash().Hex(), time.Since(submitTime).Seconds(), retries, firstTxHash)
  320.             if retries == 0 {
  321.                 break
  322.             }
  323.             atomic.AddInt64(&client.processingRetries, -1) // отпустили ретрай когда успешно сработал
  324.             return C.CString(
  325.                 fmt.Sprintf(
  326.                     "{\"error\":\"tx was cancelled with %s\", \"tx_hash\":\"%s\"}",
  327.                     tx.Hash().Hex(), firstTxHash,
  328.                 ),
  329.             )
  330.         }
  331.     }
  332.  
  333.     if receipt.Status != 1 {
  334.         log.Printf("{\n  tx = %s failed\n  exec_time = %fs\n}", tx.Hash().Hex(), time.Since(submitTime).Seconds())
  335.         msg := ethereum.CallMsg{
  336.             To:   &receipt.ContractAddress,
  337.             Data: tx.Data(),
  338.         }
  339.         errorMsg, err := client.innerClient.CallContract(context.Background(), msg, receipt.BlockNumber)
  340.         if err != nil {
  341.             return C.CString(
  342.                 fmt.Sprintf(
  343.                     "{\"error\":\"calling contract error: %v\", \"tx_hash\":\"%s\"}",
  344.                     err, tx.Hash().Hex(),
  345.                 ),
  346.             )
  347.         }
  348.         if len(errorMsg) > 0 {
  349.             return C.CString(
  350.                 fmt.Sprintf(
  351.                     "{\"error\":\"execution reverted error msg: %s\", \"tx_hash\":\"%s\"}",
  352.                     string(errorMsg), tx.Hash().Hex(),
  353.                 ),
  354.             )
  355.         }
  356.         return C.CString(
  357.             fmt.Sprintf(
  358.                 "{\"error\":\"unknown tx error\", \"tx_hash\":\"%s\"}",
  359.                 tx.Hash().Hex(),
  360.             ),
  361.         )
  362.     }
  363.  
  364.     log.Printf(
  365.         "golang tx log: {\n  tx = %s ok;\n  status = %d\n  block = %d\n  usedGas = %d\n exec_time = %fs\n}",
  366.         tx.Hash().Hex(), receipt.Status, receipt.BlockNumber.Int64(), receipt.GasUsed, time.Since(submitTime).Seconds(),
  367.     )
  368.  
  369.     return C.CString(
  370.         fmt.Sprintf(
  371.             "{\"error\":\"ok\", \"block\":\"%d\", \"used_gas\":\"%d\", \"tx_hash\":\"%s\"}",
  372.             receipt.BlockNumber.Int64(), receipt.GasUsed, tx.Hash().Hex(),
  373.         ),
  374.     )
  375. }
  376.  
  377. //export GetCoreUserIdFromJwt
  378. func GetCoreUserIdFromJwt(token *C.char) *C.char {
  379.     id, err := getJwtTokenField(C.GoString(token), "MetaCoreId")
  380.     if err != nil {
  381.         log.Fatalf("GetUserIdFromJwt error: %v", err)
  382.     }
  383.     return C.CString(id)
  384. }
  385.  
  386. func main() {}
  387.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement