Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions checker/checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package checker

import (
"context"
"fmt"
"time"

"github.com/reddio-com/reddio/checker/client"
)

type Checker struct {
c *client.NodeClient
CurrStatus map[string]uint64
currBlock uint64
}

func NewChecker(host string) *Checker {
return &Checker{
c: client.NewNodeClient(host),
}
}

func (c *Checker) Run(ctx context.Context) error {
if err := c.Init(); err != nil {
return err
}
for {
select {
case <-ctx.Done():
return nil
default:
latestBlock, err := c.c.GetLatestBlock()
if err != nil {
return err
}
if latestBlock > c.currBlock {
if err := c.CheckBlock(c.currBlock); err != nil {
return err
}
c.currBlock++
} else {
time.Sleep(3 * time.Second)
}
}
}
}

func (c *Checker) Init() error {
latestBlock, err := c.c.GetLatestBlock()
if err != nil {
return err
}
c.currBlock = latestBlock
return nil
}

func (c *Checker) CheckBlock(blockNum uint64) error {
c.CurrStatus = make(map[string]uint64)
addrs := make(map[string]struct{})
txns, err := c.c.GetBlockByNumber(blockNum)
if err != nil {
return fmt.Errorf("GetBlockByNumber %v err: %v", blockNum, err)
}
for _, txn := range txns {
if IsTransfer(txn) {
addrs[txn.From] = struct{}{}
addrs[txn.To] = struct{}{}
}
}
before, err := c.GetBlockBalance(addrs, blockNum-1)
if err != nil {
return err
}
cal := make(map[string]uint64)
for addr, v := range before {
cal[addr] = v
}
now, err := c.GetBlockBalance(addrs, blockNum)
if err != nil {
return err
}
for _, txn := range txns {
if IsTransfer(txn) {
cal[txn.From] -= txn.Value + txn.Gas*txn.GasPrice
cal[txn.To] += txn.Value
}
}
for addr, v := range cal {
fmt.Println(fmt.Sprintf("addr: %s, expect: %d, actual: %d", addr, v, now[addr]))
}
return nil
}

func (c *Checker) GetBlockBalance(addrs map[string]struct{}, blockNum uint64) (map[string]uint64, error) {
blockBalance := map[string]uint64{}
for addr := range addrs {
v, err := c.c.GetBalanceByBlock(blockNum, addr)
if err != nil {
return nil, err
}
blockBalance[addr] = v
}
return blockBalance, nil
}

func IsTransfer(txn *client.BlockTransaction) bool {
if txn.Input != "0x" {
return false
}
return true
}
209 changes: 209 additions & 0 deletions checker/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package client

import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"net/http"
"strconv"
)

type NodeClient struct {
host string
http *http.Client
}

func NewNodeClient(host string) *NodeClient {
return &NodeClient{
host: host,
http: &http.Client{},
}
}

type rpcRequest struct {
JsonRpc string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
Id int `json:"id"`
}

type rpcResponse struct {
JsonRpc string `json:"jsonrpc"`
Id int `json:"id"`
Result json.RawMessage `json:"result"`
Error *rpcError `json:"error"`
}

type rpcError struct {
Code int `json:"code"`
Message string `json:"message"`
}

func (client *NodeClient) sendRequest(method string, params []interface{}) (json.RawMessage, error) {
request := rpcRequest{
JsonRpc: "2.0",
Method: method,
Params: params,
Id: 1,
}

requestBody, err := json.Marshal(request)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %v", err)
}

resp, err := client.http.Post(client.host, "application/json", bytes.NewBuffer(requestBody))
if err != nil {
return nil, fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()

var response rpcResponse
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode response: %v", err)
}

if response.Error != nil {
return nil, fmt.Errorf("RPC error: %d - %s", response.Error.Code, response.Error.Message)
}
return response.Result, nil
}

type BlockTransaction struct {
Hash string `json:"hash"`
From string `json:"from"`
To string `json:"to"`
Value uint64 `json:"value"`
Gas uint64 `json:"gas"`
GasPrice uint64 `json:"gasPrice"`
Input string `json:"input"`
Nonce uint64 `json:"nonce"`
}

func parseHexUint64(hexStr string) (uint64, error) {
if hexStr == "" || hexStr == "0x" {
return 0, nil
}
val := new(big.Int)
if _, ok := val.SetString(hexStr[2:], 16); !ok {
return 0, fmt.Errorf("invalid hex number: %s", hexStr)
}
if !val.IsUint64() {
return 0, fmt.Errorf("value exceeds uint64 range: %s", hexStr)
}
return val.Uint64(), nil
}

func parseTransaction(data json.RawMessage) (*BlockTransaction, error) {
var tx struct {
Hash string `json:"hash"`
From string `json:"from"`
To string `json:"to"`
Value string `json:"value"`
Gas string `json:"gas"`
GasPrice string `json:"gasPrice"`
Input string `json:"input"`
Nonce string `json:"nonce"`
}
if err := json.Unmarshal(data, &tx); err != nil {
return nil, err
}

value, err := parseHexUint64(tx.Value)
if err != nil {
return nil, fmt.Errorf("failed to parse value: %v", err)
}

gas, err := parseHexUint64(tx.Gas)
if err != nil {
return nil, fmt.Errorf("failed to parse gas: %v", err)
}

gasPrice, err := parseHexUint64(tx.GasPrice)
if err != nil {
return nil, fmt.Errorf("failed to parse gasPrice: %v", err)
}

nonce, err := parseHexUint64(tx.Nonce)
if err != nil {
return nil, fmt.Errorf("failed to parse nonce: %v", err)
}

return &BlockTransaction{
Hash: tx.Hash,
From: tx.From,
To: tx.To,
Value: value,
Gas: gas,
GasPrice: gasPrice,
Input: tx.Input,
Nonce: nonce,
}, nil
}

func (client *NodeClient) GetBlockByNumber(blockNumber uint64) ([]*BlockTransaction, error) {
hexBlockNumber := "0x" + strconv.FormatUint(blockNumber, 16)
result, err := client.sendRequest("eth_getBlockByNumber", []interface{}{hexBlockNumber, true})
if err != nil {
return nil, err
}

var block struct {
Transactions []json.RawMessage `json:"transactions"`
}
if err := json.Unmarshal(result, &block); err != nil {
return nil, fmt.Errorf("failed to unmarshal block: %v", err)
}

var txs []*BlockTransaction
for _, txData := range block.Transactions {
tx, err := parseTransaction(txData)
if err != nil {
return nil, fmt.Errorf("failed to parse transaction: %v", err)
}
txs = append(txs, tx)
}

return txs, nil
}

func (client *NodeClient) GetLatestBlock() (uint64, error) {
result, err := client.sendRequest("eth_blockNumber", nil)
if err != nil {
return 0, err
}

var hexBlockNumber string
if err := json.Unmarshal(result, &hexBlockNumber); err != nil {
return 0, fmt.Errorf("failed to unmarshal block number: %v", err)
}

blockNumber, err := strconv.ParseUint(hexBlockNumber[2:], 16, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse block number: %v", err)
}

return blockNumber, nil
}

func (client *NodeClient) GetBalanceByBlock(blockNumber uint64, address string) (uint64, error) {
hexBlockNumber := "0x" + strconv.FormatUint(blockNumber, 16)
result, err := client.sendRequest("eth_getBalance", []interface{}{address, hexBlockNumber})
if err != nil {
return 0, err
}

var hexBalance string
if err := json.Unmarshal(result, &hexBalance); err != nil {
return 0, fmt.Errorf("failed to unmarshal balance: %v", err)
}

balance := new(big.Int)
balance.SetString(hexBalance[2:], 16)
if !balance.IsUint64() {
return 0, fmt.Errorf("balance is too large to fit in uint64")
}

return balance.Uint64(), nil
}
34 changes: 34 additions & 0 deletions cmd/checker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"context"
"flag"
"os"
"os/signal"
"syscall"

"github.com/reddio-com/reddio/checker"
)

var (
host string
)

func init() {
flag.StringVar(&host, "host", "http://localhost:9092", "server host")
}

func main() {
flag.Parse()
c := checker.NewChecker(host)
ctx, cancel := context.WithCancel(context.Background())
go func() {
c.Run(ctx)
}()
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, syscall.SIGTERM)
select {
case <-sigint:
cancel()
}
}