2

从零开发区块链应用(十六)--ETH转账处理

 2 years ago
source link: https://learnblockchain.cn/article/3508
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

从零开发区块链应用(十六)--ETH转账处理

一、ETH转账

在本章节中,你将学习如何将ETH从一个帐户转移到另一个帐户。如果你已熟悉以太坊,那么你就知道如何交易包括你打算转账的以太币数量量,燃气限额,燃气价格,一个随机数(non...

一、ETH转账

在本章节中,你将学习如何将ETH从一个帐户转移到另一个帐户。如果你已熟悉以太坊,那么你就知道如何交易包括你打算转账的以太币数量量,燃气限额,燃气价格,一个随机数(nonce),接收地址以及可选择性的添加的数据。 在广告发送到网络之前,必须使用发送方的私钥对该交易进行签名。

假设你已经连接了客户端,下一步就是加载你的私钥。在本课程中,你将学习如何将ETH从一个帐户转移到另一个帐户。如果你已熟悉以太坊,那么你就知道如何交易包括你打算转账的以太币数量量,燃气限额,燃气价格,一个随机数(nonce),接收地址以及可选择性的添加的数据。 在广告发送到网络之前,必须使用发送方的私钥对该交易进行签名。

假设你已经连接了节点客户端,下一步就是获取from地址的私钥。

// 生成资金池的私钥
pri, _ := wallet.NewMasterKey("")

该函数需要我们发送的帐户的公共地址 - 这个我们可以从私钥派生。

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
  log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
}

fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

如果我们已确认from地址,则可以直接定义一个变量就可以,从私钥中派生出from地址判断是否一致即可

//定义from地址
from := "0xa2fd8293e0e29286650c235d736606c31f422428"
    // 生成资金池的私钥
	pri, _ := wallet.NewMasterKey("")
	// 判断地址是否相等
	// 判断用户地址 与 使用user_id生成的地址是否一致,如果不是,则返回false
	if addr != wallet.NewAccount(userid) {
		return false
	}

下一步是设置我们将要转移的ETH数量。 但是我们必须将ETH以太转换为wei,因为这是以太坊区块链所使用的。 以太网支持最多18个小数位,因此1个ETH为1加18个零。 这里有一个小工具可以帮助您在ETH和wei之间进行转换: https://etherconverter.netlify.com

value, _ := new(big.Int).SetString(amount, 10) // in wei (1 eth)

ETH转账的燃气应设上限为“21000”单位。

gas, _ := new(big.Int).SetString("210000", 10) // in units

燃气价格必须以wei为单位设定。 在撰写本文时,将在一个区块中比较快的打包交易的燃气价格为50 gwei。

gasPrice, _ := new(big.Int).SetString("5 0000000000", 10) // in wei (50 gwei)

然而,燃气价格总是根据市场需求和用户愿意支付的价格而波动的,因此对燃气价格进行硬编码有时并不理想。 go-ethereum客户端提供SuggestGasPrice函数,用于根据'x'个先前块来获得平均燃气价格。

gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
  log.Fatal(err)
}

接下来我们弄清楚我们将ETH发送给谁。

toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")

或者直接接收用户的传参

tostr := addr
to := ecdsa.HexToAddress(toStr)

之后我们需要获得帐户的随机数(nonce)。 每笔交易都需要一个nonce。 根据定义,nonce是仅使用一次的数字。 如果是发送交易的新帐户,则该随机数将为“0”。 来自帐户的每个新事务都必须具有前一个nonce增加1的nonce。很难对所有nonce进行手动跟踪,于是ethereum客户端提供一个帮助方法GetTransactionCount,它将返回你应该使用的下一个nonce。

首先将nonce值进行加锁,防止交易并发

// 将nonce值进行加锁
	nonce.Lock.Lock()
	// 代码执行后,解锁nonce值
	defer nonce.Lock.Unlock()

接下来我们可以读取我们应该用于帐户交易的随机数

// 获取当前from地址的nonce值
	if true {
		nonce.Number, err = rc.Client.GetTransactionCount(from)
	}
	if err != nil {
		return "", fmt.Errorf("SendRawTransactionNonce s.GetNonce err:%v", err.Error())
	}
	// nonce := uint64(11)

现在我们最终可以通过导入go-ethereumcore/types包并调用NewTransaction来生成我们的未签名以太坊事务,这个函数需要接收nonce,地址,值,燃气上限值,燃气价格和可选发的数据。 发送ETH的数据字段为“nil”。 在与智能合约进行交互时,我们将使用数据字段,仅仅转账以太币是不需要数据字段的。

以下将用if判断该交易为普通转账还是合约转账

//判断to地址长度是否为0000711*987456.001447745632110.
	//判断to地址是否为0
	if len(toStr) == 0 {
		//如果data为空
		//如果to地址为0,data值为空,则返回报错
		if data == nil {
			return "", fmt.Errorf("SendRawTransactionNonce data parameter error")
		}
		//to地址为0,data不为空,说明该交易为合约交易,则对以太坊交易进行签名
		tx = NewContractCreation(nonce.Number, value, gasLimit, gasPrice, data)
	} else {
		tx = NewTransaction(nonce.Number, to, value, gasLimit, gasPrice, data)
	}
	//to地址不为0,,说明该交易为普通交易,则对以太坊交易进行签名
	tx, err = SignTx(tx, NewHubbleSigner(rc.ChainID), priKeyBuf)
	if err != nil {
		return "", fmt.Errorf("SendRawTransaction SignTx err:%v", err.Error())
	}

下一步是针对已签名的交易进行rlp序列化。

// rlp序列化
	txParam, err := rlp.EncodeToBytes(tx)
	if err != nil {
		return "", fmt.Errorf("SendRawTransaction rlp.EncodeToBytes tx err:%v", err.Error())
	}

接下来调用交易发送的方法

info, err := rc.Client.SendContract(conv.ToHex(txParam))
	if err != nil {
		logger.Error("SendRawTransaction", "step", "SendContract", "nonce", nonce.Number, "err", err.Error())
		return "", err
	}
	res, ok := info.(string)
	if !ok {
		logger.Error("SendRawTransaction", "step", "HexToString", "err", err.Error())
		return "", err
	}

现在我们终于准备要将已签名的事务广播到整个网络。

// SendContract 发送合约
func (eth *Http) SendContract(data string) (interface{}, error) {
	args = []interface{}{data}
	params := NewHttpParams("eth_sendRawTransaction", args)
	resBody, err := eth.rpc.HttpRequest(params)
	if err != nil {
		return nil, err
	}
	return eth.ParseJsonRPCResponse(resBody)
}

二、完整代码

用户调用转账的方法

func tranfer(userid, addr, amount string) bool {
	from := "0xa2fd8293e0e29286650c235d736606c31f422428"
	to := addr
    // 生成资金池的私钥
	pri, _ := wallet.NewMasterKey("")
	// 判断地址是否相等
	// 判断用户地址 与 使用user_id生成的地址是否一致,如果不是,则返回false
	if addr != wallet.NewAccount(userid) {
		return false
	}
	//将字符串类型的值转换为10进制的整数类型
	gas, _ := new(big.Int).SetString("100000", 10)
	gasPrice, _ := new(big.Int).SetString("5000000000", 10)

	value, _ := new(big.Int).SetString(amount, 10)
	input := []byte("0x0")

	result, err := rc.SendRawTransaction(from, to, pri.ToHex(), value, gas, gasPrice, input)
	if err != nil {
		logger.Error("tranfer", "step", "SendRawTransaction", "addr", addr, "err", err)
		return false
	}

	logger.Warn("tranfer", "step", "result", "addr", addr, "hash", result)
	model.UserWalletUpdateMOBA(userid, weiToEth(amount))
	return true
}

交易签名方法

// SendRawTransaction 发送签名交易
func (rc *RequestChain) SendRawTransaction(from, toStr, hexPriKey string, value, gas, gasPrice *big.Int, data []byte) (string, error) {
	// 预执行交易GetEstimateGas
	/*inputStr := conv.ToHex(data)
	valueStr := strconv.FormatInt(value.Int64(), 16)
	logger.Info("GetEstimateGas", "inputStr", inputStr, "valueStr", valueStr)
	mayGas, err := rc.Client.GetEstimateGas(from, toStr, conv.ToHex(data), "0x"+strconv.FormatInt(value.Int64(), 16))
	if err != nil {
		return "", fmt.Errorf("GetEstimateGas run err:%v", err.Error())
	}
	// 使用的gas
	spareGas := mayGas + mayGas/3

	if gas.Int64() < spareGas {
		gas = big.NewInt(spareGas)
	}*/
	// 获取私钥
	// 获取私钥,将字符串转为byte
	priKeyBuf, err := hex.DecodeString(hexPriKey)
	if err != nil {
		return "", fmt.Errorf("SendRawTransactionNonce parameter hexPriKey err:%v", err.Error())
	}
	// 判断nonce值

	// 将nonce值进行加锁
	nonce.Lock.Lock()
	// 代码执行后,解锁nonce值
	defer nonce.Lock.Unlock()

	// 获取当前from地址的nonce值
	if true {
		nonce.Number, err = rc.Client.GetTransactionCount(from)
	}
	if err != nil {
		return "", fmt.Errorf("SendRawTransactionNonce s.GetNonce err:%v", err.Error())
	}
	// nonce := uint64(11)

	//将string类型的to地址转为一个对象
	to := ecdsa.HexToAddress(toStr)
	var gasLimit uint64 = 0
	if nil == gas {
		gasLimit = 0
	} else {
		gasLimit = gas.Uint64()
	}

	// Construct transaction object
	var tx *Transaction

	//判断to地址长度是否为0000711*987456.001447745632110.
	//判断to地址是否为0
	if len(toStr) == 0 {
		//如果data为空
		//如果to地址为0,data值为空,则返回报错
		if data == nil {
			return "", fmt.Errorf("SendRawTransactionNonce data parameter error")
		}
		//to地址为0,data不为空,说明该交易为合约交易,则对以太坊交易进行签名
		tx = NewContractCreation(nonce.Number, value, gasLimit, gasPrice, data)
	} else {
		tx = NewTransaction(nonce.Number, to, value, gasLimit, gasPrice, data)
	}
	//to地址不为0,,说明该交易为普通交易,则对以太坊交易进行签名
	tx, err = SignTx(tx, NewHubbleSigner(rc.ChainID), priKeyBuf)
	if err != nil {
		return "", fmt.Errorf("SendRawTransaction SignTx err:%v", err.Error())
	}

	// rlp序列化
	txParam, err := rlp.EncodeToBytes(tx)
	if err != nil {
		return "", fmt.Errorf("SendRawTransaction rlp.EncodeToBytes tx err:%v", err.Error())
	}
	info, err := rc.Client.SendContract(conv.ToHex(txParam))
	if err != nil {
		logger.Error("SendRawTransaction", "step", "SendContract", "nonce", nonce.Number, "err", err.Error())
		return "", err
	}
	res, ok := info.(string)
	if !ok {
		logger.Error("SendRawTransaction", "step", "HexToString", "err", err.Error())
		return "", err
	}
	return res, nil
}

广播以太坊交易方法

// SendContract 发送合约
func (eth *Http) SendContract(data string) (interface{}, error) {
	args = []interface{}{data}
	params := NewHttpParams("eth_sendRawTransaction", args)
	resBody, err := eth.rpc.HttpRequest(params)
	if err != nil {
		return nil, err
	}
	return eth.ParseJsonRPCResponse(resBody)
}

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 6天前
  • 阅读 ( 184 )
  • 学分 ( 14 )

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK