Fteepromax/main.go
2025-02-21 00:31:21 +08:00

308 lines
8.8 KiB
Go

package main
import (
"context"
"crypto/ecdsa"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"golang.org/x/exp/slog"
"io/ioutil"
"log"
"math/big"
"net/http"
"strconv"
"strings"
)
// SimpleStorage 是智能合约的绑定结构体
type SimpleStorage struct {
contract *bind.BoundContract
abi abi.ABI
}
type Response struct {
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func writeResponse(w http.ResponseWriter, r *Response) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(r)
}
// NewSimpleStorage 创建一个新的 SimpleStorage 实例
func NewSimpleStorage(address common.Address, backend bind.ContractBackend, abiJSON string) (*SimpleStorage, error) {
parsedAbi, err := abi.JSON(strings.NewReader(abiJSON))
if err != nil {
slog.Error("解析 ABI 失败", "error", err)
return nil, err
}
contract := bind.NewBoundContract(address, parsedAbi, backend, backend, backend)
slog.Info("SimpleStorage 实例已创建", "地址", address.Hex())
return &SimpleStorage{contract: contract, abi: parsedAbi}, nil
}
// Call 调用合约的只读方法
func (s *SimpleStorage) Call(opts *bind.CallOpts, method string, args ...interface{}) ([]string, error) {
var result []interface{}
slog.Info("调用合约只读方法", "方法", method, "参数", args)
err := s.contract.Call(opts, &result, method, args...)
if err != nil {
slog.Error("调用合约失败", "方法", method, "参数", args, "error", err)
return nil, err
}
var resultValues []string
for _, res := range result {
if str, ok := res.(string); ok {
resultValues = append(resultValues, str)
} else {
return nil, fmt.Errorf("unexpected result type: %T", res)
}
}
slog.Info("合约调用返回结果", "结果", resultValues)
return resultValues, nil
}
// Transact 调用合约的写入方法
func (s *SimpleStorage) Transact(auth *bind.TransactOpts, method string, args ...interface{}) (*types.Transaction, error) {
slog.Info("调用合约写入方法", "方法", method, "参数", args)
tx, err := s.contract.Transact(auth, method, args...)
if err != nil {
slog.Error("调用合约失败", "方法", method, "参数", args, "error", err)
return nil, err
}
slog.Info("交易已发送", "交易哈希", tx.Hash().Hex())
return tx, nil
}
var (
contractAddress = common.HexToAddress("0x4d76E676Efc348045a285f26Bb4B462328DE1d2c")
localNetworkURL = "http://localhost:7545"
abiJSON []byte
privateKey string
)
type Config struct {
PrivateKey string `json:"privateKey"`
}
func loadConfig(configPath string) (*Config, error) {
data, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, err
}
var config Config
err = json.Unmarshal(data, &config)
if err != nil {
return nil, err
}
return &config, nil
}
func setHandler(client *ethclient.Client, storage *SimpleStorage, privateKeyECDSA *ecdsa.PrivateKey) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
keyStr := r.URL.Query().Get("key")
value := r.URL.Query().Get("value")
keyUint, err := strconv.ParseUint(keyStr, 10, 64)
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: "无效的键值类型,必须为整数"})
return
}
// 动态获取当前网络的链 ID
chainID, err := client.NetworkID(context.Background())
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: "无法获取网络链 ID"})
return
}
auth, err := bind.NewKeyedTransactorWithChainID(privateKeyECDSA, chainID)
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: "创建交易授权失败"})
return
}
bigKey := big.NewInt(0).SetUint64(keyUint)
tx, err := storage.Transact(auth, "set", bigKey, value)
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: fmt.Sprintf("设置值失败: %v", err)})
return
}
writeResponse(w, &Response{Msg: "ok", Data: tx.Hash().Hex()})
}
}
func getHandler(storage *SimpleStorage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
keyStr := r.URL.Query().Get("key")
if keyStr == "" {
writeResponse(w, &Response{Msg: "error", Data: "缺少键值"})
return
}
keyUint, err := strconv.ParseUint(keyStr, 10, 64)
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: "无效的键值类型,必须为整数"})
return
}
bigKey := big.NewInt(0).SetUint64(keyUint)
opts := &bind.CallOpts{
Context: context.Background(),
}
result, err := storage.Call(opts, "get", bigKey)
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: fmt.Sprintf("获取值失败: %v", err)})
return
}
writeResponse(w, &Response{Msg: "ok", Data: result[0]})
}
}
// getByTxHashHandler 通过交易哈希查询存储在智能合约中的值
func getByTxHashHandler(client *ethclient.Client, storage *SimpleStorage) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
txHashStr := r.URL.Query().Get("txHash")
if txHashStr == "" {
writeResponse(w, &Response{Msg: "error", Data: "缺少 txHash 参数"})
return
}
// 通过交易哈希获取键值
txHash := common.HexToHash(txHashStr)
// 获取交易详情
tx, _, err := getTransactionDetails(client, txHash)
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: fmt.Sprintf("无法获取交易详情: %v", err)})
return
}
// 解析交易输入
methodName, args, err := parseTransactionInput(string(abiJSON), tx.Data())
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: fmt.Sprintf("无法解析交易输入: %v", err)})
return
}
// 假设方法名为 "set",并且第一个参数是键
if methodName == "set" && len(args) > 0 {
key, ok := args[0].(*big.Int)
if !ok {
writeResponse(w, &Response{Msg: "error", Data: "键值类型不匹配"})
return
}
// 通过键值调用智能合约的 get 方法
callOpts := &bind.CallOpts{
Pending: false,
}
result, err := storage.Call(callOpts, "get", key)
if err != nil {
writeResponse(w, &Response{Msg: "error", Data: fmt.Sprintf("获取数据失败: %v", err)})
return
}
// 假设结果只包含一个字符串值
if len(result) > 0 {
writeResponse(w, &Response{Msg: "ok", Data: result[0]})
} else {
writeResponse(w, &Response{Msg: "error", Data: "未从合约中获得任何结果"})
}
} else {
writeResponse(w, &Response{Msg: "error", Data: "无效的方法或参数"})
}
}
}
// getTransactionDetails 获取交易详情
func getTransactionDetails(client *ethclient.Client, txHash common.Hash) (*types.Transaction, *types.Receipt, error) {
tx, _, err := client.TransactionByHash(context.Background(), txHash)
if err != nil {
return nil, nil, fmt.Errorf("无法获取交易: %v", err)
}
receipt, err := client.TransactionReceipt(context.Background(), txHash)
if err != nil {
return nil, nil, fmt.Errorf("无法获取交易收据: %v", err)
}
return tx, receipt, nil
}
// parseTransactionInput 解析交易输入
func parseTransactionInput(abiJSON string, input []byte) (methodName string, args []interface{}, err error) {
parsedAbi, err := abi.JSON(strings.NewReader(abiJSON))
if err != nil {
return "", nil, fmt.Errorf("解析 ABI 失败: %v", err)
}
method, err := parsedAbi.MethodById(input[:4])
if err != nil {
return "", nil, fmt.Errorf("无法找到方法: %v", err)
}
decodedArgs, err := method.Inputs.Unpack(input[4:])
if err != nil {
return "", nil, fmt.Errorf("解包输入失败: %v", err)
}
return method.Name, decodedArgs, nil
}
func main() {
// 加载配置文件
config, err := loadConfig("config.json")
if err != nil {
log.Fatalf("加载配置文件失败: %v", err)
}
privateKey = config.PrivateKey
// 连接到本地以太坊节点
client, err := ethclient.Dial(localNetworkURL)
if err != nil {
log.Fatalf("连接到以太坊节点失败: %v", err)
}
// 加载 ABI JSON
abiJSON, err = ioutil.ReadFile("SimpleStorage.abi")
if err != nil {
log.Fatalf("加载 ABI 文件失败: %v", err)
}
// 创建 SimpleStorage 实例
storage, err := NewSimpleStorage(contractAddress, client, string(abiJSON))
if err != nil {
log.Fatalf("创建 SimpleStorage 实例失败: %v", err)
}
// 解析私钥
privateKeyECDSA, err := crypto.HexToECDSA(privateKey)
if err != nil {
log.Fatalf("解析私钥失败: %v", err)
}
// 设置 HTTP 处理函数
http.HandleFunc("/set", setHandler(client, storage, privateKeyECDSA))
http.HandleFunc("/get", getHandler(storage))
http.HandleFunc("/getByTxHash", getByTxHashHandler(client, storage))
// 启动 HTTP 服务器
log.Println("启动 HTTP 服务器...")
log.Fatal(http.ListenAndServe(":8080", nil))
}