برنامه نویسی

ساختن تماشاگر کیف پول اتریوم با استفاده از برنامه نویسی همزمان در GoLang

عکس روی جلد توسط Mika Baumeister / Unsplash

در آخرین مقاله خود در مورد اینکه چقدر ساده است ساخت برنامه هایی که از برنامه نویسی همزمان با استفاده از زبان Go استفاده می کنند صحبت کردم، دیدیم که پیاده سازی آن چقدر ساده است و چقدر می تواند کارآمد باشد، به لطف ساخت آن که ابزارهای قدرتمندی را در اختیار توسعه دهندگان قرار می دهد. برای استفاده کامل از پتانسیل برنامه نویسی همزمان. در این مقاله نگاهی گام به گام عملی به نحوه ساخت یک واچ کیف پول اتریوم با استفاده از این قدرت GoLang خواهیم داشت.

متن نوشته

وب 3 از زمانی که به مطالعات برنامه نویسی خود بازگشتم همیشه موضوعی بوده است که بسیار به آن علاقه مند هستم، من از علاقه مندان به فناوری بلاک چین در پشت اکثر محصولات توسعه یافته هستم و به اصطلاح اخیراً در راه حل های سنتی تر مورد استفاده قرار گرفته است. برخی از کیف پول‌ها کلیدهای خصوصی خود را به صورت عمومی در معرض دید عموم قرار می‌دهند، اکثر آنها توسط کیت‌های توسعه در دسترس هستند که می‌توانید یک گره آزمایشی را در یک محیط محلی اجرا کنید. برخی از کاربران با بی دقتی و عدم توجه، در نهایت مقادیری را به آدرس این کیف پول در شبکه اصلی (به اصطلاح در حال تولید) ارسال می کنند.

هدف

ما یک سرویس در GoLang ایجاد خواهیم کرد تا کیف پول‌های اتریوم را بررسی کنیم و اگر تراکنش‌های موجودی ورودی وجود داشته باشد، سعی می‌کنیم یک تراکنش خروجی برای دریافت آن مقادیر انجام دهیم.

برای مثال بیشتر استفاده از برنامه نویسی همزمان، یک API برای جستجوی اطلاعات از یک آدرس اتریوم نیز ارائه خواهیم کرد.

به طور خلاصه، ما سرویس Wallet Watcher خود را در کنار یک استراحت API اجرا خواهیم کرد. بیا بریم؟

قبل از اینکه شروع کنیم

سورس کد این پروژه در GitHub من است، شما می توانید آن را با استفاده از git clone و اجرای آن بر روی دستگاه خود کلون کنید، فراموش نکنید که یک فایل .env در ریشه پروژه به دنبال .env.example ایجاد کنید.

git clone https://github.com/ronilsonalves/go-wallet-watcher.git
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر همچنین می خواهید Rest API را برای پرس و جوها بسازید، باید یک کلید API از Etherscan.io داشته باشید، برای این کار باید یک حساب کاربری داشته باشید و یک کلید رایگان در https://etherscan.io/myapikey درخواست کنید.

شروع شدن

جایی که پروژه خود را ذخیره می کنیم و آن را شروع می کنیم، برای این کار، در ترمینال باید تایپ کنیم:

go mod init 'project-name'
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قبل از ادامه، بیایید ماژول های go-ethereum و godotenv را نصب کنیم که به ما در ساخت سرویس کمک می کند:

go get github.com/ethereum/go-ethereum
go get github.com/joho/godotenv
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حال باید یک دایرکتوری به نام داخلی ایجاد کنیم که در آن فایل های داخلی پروژه خود را سازماندهی کنیم، در داخل آن پوشه دایرکتوری دیگری ایجاد کنیم که بسته دامنه ما خواهد بود و در نهایت فایل wallet.go خود را ایجاد می کنیم که حاوی یک ساختاری که یک کیف پول اتریوم را نشان می دهد:

package domain

type Wallet struct {
    Address      string        `json:"address"`
    SecretKey    string        `json:"secret-key,omitempty"`
    Balance      float64       `json:"balance"`
    Transactions []Transaction `json:"transactions,omitempty"`
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همچنین، ما باید یک ساختار برای نشان دادن یک تراکنش ایجاد کنیم، در ادامه از آن استفاده خواهیم کرد:

package domain

type Transaction struct {
    BlockNumber       string `json:"blockNumber,omitempty"`
    TimeStamp         string `json:"timeStamp,omitempty"`
    Hash              string `json:"hash,omitempty"`
    Nonce             string `json:"nonce,omitempty"`
    BlockHash         string `json:"blockHash,omitempty"`
    TransactionIndex  string `json:"transactionIndex,omitempty"`
    From              string `json:"from,omitempty"`
    To                string `json:"to,omitempty"`
    Value             string `json:"value,omitempty"`
    Gas               string `json:"gas,omitempty"`
    GasPrice          string `json:"gasPrice,omitempty"`
    IsError           string `json:"isError,omitempty"`
    TxreceiptStatus   string `json:"txreceipt_status,omitempty"`
    Input             string `json:"input,omitempty"`
    ContractAddress   string `json:"contractAddress,omitempty"`
    CumulativeGasUsed string `json:"cumulativeGasUsed,omitempty"`
    GasUsed           string `json:"gasUsed,omitempty"`
    Confirmations     string `json:"confirmations,omitempty"`
    MethodId          string `json:"methodId,omitempty"`
    FunctionName      string `json:"functionName,omitempty"`
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ایجاد تماشاگر کیف پول ما با استفاده از گوروتین ها

هنوز در دایرکتوری داخلی، بسته دیگری به نام “watcher” ایجاد می کنیم، در داخل آن فایل service.go خود را ایجاد می کنیم که در آن تماشاگر خود را پیاده سازی می کنیم، ابتدا تابعی را ایجاد می کنیم که مسئول راه اندازی سرویس خود است:

// StartWatcherService load from environment the data and start running goroutines to perform wallet watcher service.
func StartWatcherService() {

    err := godotenv.Load()
    if err != nil {
        log.Fatalln("Error loading .env file", err.Error())
    }

    var wfe [20]domain.Wallet
    var wallets []domain.Wallet

    for index := range wfe {
        wallet := domain.Wallet{
            Address:   os.Getenv("WATCHER_WALLET" + strconv.Itoa(index+1)),
            SecretKey: os.Getenv("WATCHER_SECRET" + strconv.Itoa(index+1)),
        }
        wallets = append(wallets, wallet)
    }
    // contains filtered fields or functions
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در قطعه کد بالا، متغیرهای محیطی خود را بارگذاری می‌کنیم، جایی که داده‌هایی را که نباید در معرض نمایش قرار گیرند، ذخیره می‌کنیم، در این مثال، 20 کیف پول و کلیدهای خصوصی مربوط به آنها را با استفاده از محدوده for بارگذاری می‌کنیم.

هنوز در service.go ما، یک گروه همگام‌سازی برای گوروتین‌های خود ایجاد خواهیم کرد:

// StartWatcherService load from environment the data and start running goroutines to perform wallet watcher service.
func StartWatcherService() {

    // contains filtered fields or functions

    // Create a wait group to synchronize goroutines
    var wg sync.WaitGroup
    wg.Add(len(wallets))

    // contains filtered fields or functions
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در ادامه، ما گوروتین های خود را ایجاد خواهیم کرد:

// StartWatcherService load from environment the data and start runing goroutines to perform wallet watcher service.
func StartWatcherService() {
    // contains filtered fields or functions
    // Start a goroutine for each wallet
    for _, wallet := range wallets {
        go func(wallet domain.Wallet) {
            // Connect to the Ethereum client
            client, err := rpc.Dial(os.Getenv("WATCHER_RPC_ADDRESS"))
            if err != nil {
                log.Printf("Failed to connect to the RPC client for address %s: %v \n Trying fallback rpc server...", wallet.Address.Hex(), err)
            }
            client, err = rpc.Dial(os.Getenv("WATCHER_RPC_FALLBACK_ADDRESS"))
            if err != nil {
                log.Printf("Failed to connect to the Ethereum client for address %s: %v", wallet.Address.Hex(), err)
                wg.Done()
                return
            }

            // Create an instance of the Ethereum client
            ethClient := ethclient.NewClient(client)

            for {
                // Get the balance of the address
                balance, err := ethClient.BalanceAt(context.Background(), common.HexToAddress(wallet.Address), nil)
                if err != nil {
                    log.Printf("Failed to get balance for address %s: %v", wallet.Address.Hex(), err)
                    continue
                }

                balanceInEther := new(big.Float).Quo(new(big.Float).SetInt(balance), big.NewFloat(1e18))

                log.Printf("Balance for address %s: %.16f ETH", wallet.Address.Hex(), balanceInEther)


                // if the wallet has a balance superior to 0.0005 ETH, we are sending the balance to another wallet
                if balanceInEther.Cmp(big.NewFloat(0.0005)) > 0 {
                    sendBalanceToAnotherWallet(common.HexToAddress(wallet.Address), balance, wallet.SecretKey)
                }

                time.Sleep(300 * time.Millisecond) // Wait for a while before checking for the next block
            }
        }(wallet)
    }
        // Wait for all goroutines to finish
    wg.Wait()
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نهایت، ما تابع خود را ایجاد می کنیم که مسئول ایجاد و امضای تراکنش است که موجودی کیف پول ها را به کیف پول دیگری ارسال می کند:

// sendBalanceToAnotherWallet when find some values in any wallet perform a SendTransaction(ctx context.Context,
// tx *types.Transaction) function
func sendBalanceToAnotherWallet(fromAddress common.Address, balance *big.Int, privateKeyHex string) {
    toAddress := common.HexToAddress(os.Getenv("WATCHER_DEST_ADDRESS"))
    chainID := big.NewInt(1)

    // Connect to the Ethereum client
    client, err := rpc.Dial(os.Getenv("WATCHER_RPC_ADDRESS"))
    if err != nil {
        log.Printf("Failed to connect to the Ethereum client: %v...", err)
    }

    ethClient := ethclient.NewClient(client)

    // Load the private key
    privateKey, err := crypto.HexToECDSA(privateKeyHex[2:])
    if err != nil {
        log.Fatalf("Failed to load private key: %v", err)
    }

    // Get the current nonce for the fromAddress
    nonce, err := ethClient.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        log.Printf("Failed to retrieve nonce: %v", err)
    }

    // Create a new transaction
    gasLimit := uint64(21000) // Definimos o limite para a taxa de Gas da transação baseada no seu tipo
    gasPrice, err := ethClient.SuggestGasPrice(context.Background())
    if err != nil {
        log.Printf("Failed to retrieve gas price: %v", err)
    }


    tx := types.NewTx(&types.LegacyTx{
        Nonce:    nonce,
        GasPrice: gasPrice,
        Gas:      gasLimit,
        To:       &toAddress,
        Value:    new(big.Int).Sub(balance, new(big.Int).Mul(gasPrice, big.NewInt(int64(gasLimit)))),
        Data:     nil,
    })
    valueInEther := new(big.Float).Quo(new(big.Float).SetInt(tx.Value()), big.NewFloat(1e18))
    if valueInEther.Cmp(big.NewFloat(0)) < 0 {
        log.Println("ERROR: Insufficient funds to make transfer")
    }

    // Sign the transaction
    signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
    if err != nil {
        log.Printf("Failed to sign transaction: %v", err)
    }

    // Send the signed transaction
    err = ethClient.SendTransaction(context.Background(), signedTx)
    if err != nil {
        log.Printf("Failed to send transaction: %v", err)
    } else {
        log.Printf("Transaction sent: %s", signedTx.Hash().Hex())
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سرویس نظارت بر کیف پول ما آماده است، watcher/service.go ما باید به صورت زیر باشد:

در این مرحله، اگر نمی‌خواهیم API Rest ایجاد کنیم، فقط باید StartWatcherService() خود را به main.go خود فراخوانی کنیم.

func main() {
    // filtered fields or functions

    // Start our watcher
    go watcher.StartWatcherService()

    // Wait for the server and the watcher service to finish
    select {}
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

قرار دادن یک Rest API در معرض نمایش داده شد

ما از Gin Web Framework برای ساخت یک Rest API استفاده خواهیم کرد که در آن نقطه پایانی را در معرض پرس و جوی موجودی کیف پول و تراکنش های اخیر از یک آدرس قرار می دهیم. برای انجام این کار باید ماژول o gin-gonic را به پروژه خود اضافه کنیم:

go get github.com/gin-gonic/gin
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ایجاد یک سرویس برای بسته “کیف پول” ما

اکنون، در داخل داخلی، یک بسته کیف پول ایجاد می کنیم، در این بسته ما یک فایل service.go ایجاد می کنیم، اینجاست که با API Etherscan.io تماس می گیریم تا پرس و جوهای تراکنش و تعادل را برقرار کنیم:

type Service interface {
    GetWalletBalanceByAddress(address string) (domain.Wallet, error)
    GetTransactionsByAddress(address, page, size string) (domain.Wallet, error)
}

type service struct{}

// NewService creates a new instance of the Wallet Service.
func NewService() Service {
    return &service{}
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ابتدا روشی برای دریافت اطلاعات از آدرسی که به عنوان پارامتر دریافت خواهیم کرد ایجاد خواهیم کرد (به زودی تابع gin handler را خواهیم دید):

// GetWalletBalanceByAddress retrieves the wallet balance for the given address
func (s service) GetWalletBalanceByAddress(address string) (domain.Wallet, error) {

}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در داخل این، Etherscan.io APIKey را از محیط خود دریافت خواهیم کرد:

// GetWalletBalanceByAddress retrieves the wallet balance for the given address
func (s service) GetWalletBalanceByAddress(address string) (domain.Wallet, error) {
    // Retrieves Etherscan.io API Key from environment
    apiKey := os.Getenv("WATCHER_ETHERSCAN_API")
    url := fmt.Sprintf(fmt.Sprintf("https://api.etherscan.io/api?module=account&action=balance&address=%s&tag=latest&apikey=%s", address, apiKey))
    // Contains filtered fields or functions
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در ادامه، یک HTTP GET برای API Etherscan ایجاد می کنیم و محتوای پاسخ را می خوانیم:

// GetWalletBalanceByAddress retrieves the wallet balance for the given address
func (s service) GetWalletBalanceByAddress(address string) (domain.Wallet, error) {

    // Contains filtered fields or functions

    // Send GET request to the Etherscan API
    response, err := http.Get(url)
    if err != nil {
        log.Printf("Failed to make Etherscan API request: %v", err)
        return domain.Wallet{}, err
    }
    defer response.Body.Close()

    // Read the response body
    body, err := io.ReadAll(response.Body)
    if err != nil {
        log.Printf("Failed to read response body: %v", err)
        return domain.Wallet{}, err
    }

    // Creates a struct to represent etherscan API response
    var result struct {
        Status  string `json:"status"`
        Message string `json:"message"`
        Result  string `json:"result"`
    }

    // Contains filtered fields or functions
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نهایت ما پاسخ Etherscan.io API را تجزیه می‌کنیم و آن را مطابق با ساختار خود ساختار می‌دهیم، همچنین باید اعتبارسنجی‌هایی انجام دهیم و داده‌ها را برگردانیم:

// GetWalletBalanceByAddress retrieves the wallet balance for the given address
func (s service) GetWalletBalanceByAddress(address string) (domain.Wallet, error) {

    // Contains filtered fields or functions

    // Parse the JSON response
    err = json.Unmarshal(body, &result)
    if err != nil {
        log.Printf("Failed to parse JSON response: %v", err)
        return domain.Wallet{}, err
    }

    if result.Status != "1" {
        log.Printf("API returned error: %s", result.Message)
        return domain.Wallet{}, fmt.Errorf("API error: %s", result.Message)
    }

    wbBigInt := new(big.Int)
    wbBigInt, ok := wbBigInt.SetString(result.Result, 10)
    if !ok {
        log.Println("Failed to parse string to BigInt")
        return domain.Wallet{}, fmt.Errorf("failed to parse string into BigInt. result.Result value: %s", result.Result)
    }

    wb := new(big.Float).Quo(new(big.Float).SetInt(wbBigInt), big.NewFloat(1e18))
    v, _ := strconv.ParseFloat(wb.String(), 64)

    return domain.Wallet{
        Address: address,
        Balance: v,
    }, nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما قبلاً روش خود را برای دریافت اطلاعات موجودی از یک آدرس کیف پول داریم، اکنون بیایید روش دیگری را در فایل wallet/service.go خود ایجاد کنیم تا تراکنش ها را نشان دهیم، منطق مانند روش قبلی خواهد بود، تفاوت در نحوه انجام ما خواهد بود. پاسخ Etherscan.io API و نحوه ایجاد نقطه پایانی URL برای درخواست GET را ترسیم کنید، زیرا صفحه و تعداد موارد در هر صفحه فراتر از آدرس را به عنوان پارامتر خواهیم داشت:

// GetTransactionsByAddress retrieves the wallet balance and last transactions for the given address paggeable
func (s service) GetTransactionsByAddress(address, page, size string) (domain.Wallet, error) {
    // Fazemos a chamada para o método GetWalletBalanceByAddress para montarmos uma carteira e seu saldo
    wallet, _ := s.GetWalletBalanceByAddress(address)
    apiKey := os.Getenv("WATCHER_ETHERSCAN_API")
    url := fmt.Sprintf("https://api.etherscan.io/api?module=account&action=txlist&address=%s&startblock=0&endblock=99999999&page=%s&offset=%s&sort=desc&apikey=%s", address, page, size, apiKey)

    // Contains filtered fields or functions

    // Parse the JSON response
    var transactions struct {
        Status  string               `json:"status"`
        Message string               `json:"message"`
        Result  []domain.Transaction `json:"result"`
    }

    // Adicionamos as transações à nossa struct carteira
    wallet.Transactions = append(wallet.Transactions, transactions.Result...)

    return wallet, nil
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کیف پول/service.go خود را تمام کردیم و کل فایل باید مانند اصل زیر باشد:

ایجاد توابع gin handler برای افشای API ما

در ادامه ما gin handlerFunc خود را برای تعامل با کیف پول/service.go خود ایجاد می کنیم و نقاط پایانی لازم را برای پرس و جو از موجودی کیف پول و تراکنش های کیف پول اتریوم در معرض نمایش می گذاریم.

ما یک دایرکتوری در ریشه پروژه خود ایجاد می کنیم و آن را به عنوان cmd می کنیم (در اینجا main.go و بسته handler را از API خود قرار می دهیم، ساختار دایرکتوری باید به این صورت باشد:

.env.example
cmd
   |-- server
   |   |-- handler
   |   |   |-- wallet.go
   |   |-- main.go
internal
   |-- domain
   |   |-- transaction.go
   |   |-- wallet.go
   |-- wallet
   |   |-- dto.go
   |   |-- service.go
   |-- watcher
   |   |-- service.go
pkg
   |-- web
   |   |-- response.go
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نهایت بسته کنترل کننده خود را ایجاد می کنیم، در داخل آن یک فایل wallet.go ایجاد می کنیم:

package handler

type walletHandler struct {
    s wallet.Service
}

// NewWalletHandler creates a new instance of the Wallet Handler.
func NewWalletHandler(s wallet.Service) *walletHandler {
    return &walletHandler{s: s}
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در داخل این فایل ما دو تابع handler ایجاد خواهیم کرد: GetWalletByAddress() و GetTransactionsByAddress() برای نمایش موجودی و تراکنش های کیف پول:

// GetWalletByAddress get wallet info balance from a given address
func (h *walletHandler) GetWalletByAddress() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        ap := ctx.Param("address")
        w, err := h.s.GetWalletBalanceByAddress(ap)
        if err != nil {
            web.BadResponse(ctx, http.StatusBadRequest, "error", err.Error())
            return
        }
        web.OKResponse(ctx, http.StatusOK, w)
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

// GetTransactionsByAddress retrieves up to 10000 transactions by given adrress in a paggeable response
func (h *walletHandler) GetTransactionsByAddress() gin.HandlerFunc {
    return func(ctx *gin.Context) {
        address := ctx.Param("address")
        page := ctx.Query("page")
        size := ctx.Query("pageSize")

        if len(page) == 0 {
            page = "1"
        }
        if len(size) == 0 {
            size = "10"
        }

        if _, err := strconv.Atoi(page); err != nil {
            web.BadResponse(ctx, http.StatusBadRequest, "error", fmt.Sprintf("Invalid page param. Verify page value: %s", page))
            return
        }

        if _, err := strconv.Atoi(size); err != nil {
            web.BadResponse(ctx, http.StatusBadRequest, "error", fmt.Sprintf("Invalid pageSize param. Verify pageSize value: %s", size))
            return
        }

        response, err := h.s.GetTransactionsByAddress(address, page, size)
        if err != nil {
            web.BadResponse(ctx, http.StatusBadRequest, "error", err.Error())
            return
        }

        var pageableResponse struct {
            Page  string      `json:"page"`
            Items string      `json:"items"`
            Data  interface{} `json:"data"`
        }

        pageableResponse.Page = page
        pageableResponse.Items = size
        pageableResponse.Data = response

        web.OKResponse(ctx, http.StatusOK, pageableResponse)
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بازگشت به برنامه نویسی همزمان، در داخل فایل main.go خود، walletService و walletHandler خود را نمونه سازی می کنیم، یک سرور جین ایجاد می کنیم و آن را در داخل یک گوروتین نمونه سازی می کنیم:

func main() {

    wService := wallet.NewService()
    wHandler := handler.NewWalletHandler(wService)

    r := gin.New()
    r.Use(gin.Recovery(), gin.Logger())

    r.GET("https://dev.to/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "Everything is okay here",
        })
    })

    api := r.Group("/api/v1")
    {
        ethNet := api.Group("/eth/wallets")
        {
            ethNet.GET(":address", wHandler.GetWalletByAddress())
            ethNet.GET(":address/transactions", wHandler.GetTransactionsByAddress())
        }
    }

    // Start the Gin server in a goroutine
    go func() {
        if err := r.Run(":8080"); err != nil {
            log.Println("ERROR IN GONIC: ", err.Error())
        }
    }()

    // Contains filtered fields or functions
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما باید سرور جین خود را در داخل یک گوروتین به API خود راه اندازی کنیم و سرویس ناظر کیف پول ما به طور همزمان اجرا شود، بنابراین در نهایت، سرویس ناظر خود را در داخل گوروتین دیگری شروع می کنیم:

func main() {
    // Filtered fields or functions

    // Start our watcher
    go watcher.StartWatcherService()

    // Wait for the server and the watcher service to finish
    select {}
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما اجرای API و سرویس ناظر خود را نهایی می‌کنیم، زیرا می‌خواهیم گوروتین‌های مربوط به ناظر همچنان به اجرا درآیند در حالی که برنامه‌های برنامه ما انتخاب{} را اجرا می‌کنند، منتظر تکمیل آن‌ها می‌مانند، زمانی که ما گوروتین خود را برای هر یک از کیف‌پول‌ها ایجاد می‌کنیم. “برای” بدون بند خروج، مسئول جلوگیری از تکمیل شدن گوروتین های ما پس از اولین اجرای آنها خواهد بود:

//watcher/service.go

func StartWatcherService() {
    // Contains filtered fiels or functions

        go func(wallet domain.Wallet) {
            for {
                // Get the balance of the address
                balance, err := ethClient.BalanceAt(context.Background(), common.HexToAddress(wallet.Address), nil)

                // Contains filtered fields or functions

                time.Sleep(300 * time.Millisecond) // Wait for a while before checking for the next block

                // Contains filtered fields or functions
                }
        }
        // Esperando por todas as goroutines para finalizar - não irá finalizar por conta do for rodando pelo infinito
        wg.Wait()

        // Contains filtered fields or functions
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نتیجه

برنامه نویسی همزمان یک ابزار قدرتمند در توسعه نرم افزار است، در این مقاله/آموزش گام به گام دیدیم که چگونه آن را پیاده سازی کرده و بهترین آنها را در واچ کیف پول اتریوم استخراج کنیم، 20 کیف پول را با هزاران یا هزاران کیف پول را با هزاران کیف پول مبادله کنیم. صف‌های پیام یا جریان‌های داده، زمان‌بندی go از منابع موجود مراقبت کرده و به نحو احسن استفاده می‌کند. در مثال ما، 20 گوروتینی که به صورت همزمان در هر 300 میلی ثانیه اجرا می‌شوند، 63 مگابایت از حافظه موجود مصرف می‌کنند و استفاده از CPU 6٪ بود، این سرویس در داخل یک نمونه مشترک از 256 مگابایت از سطح رایگان Fly.io اجرا می‌شد:
مصرف حافظه
استفاده از CPU

امیدوارم این مقاله مفید بوده باشد و به شما در درک بهتر برنامه نویسی همزمان و گوروتین ها در Go کمک کرده باشد.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا