以太坊 nonce_eth以太坊交易

以太坊 nonce_eth以太坊交易1什么是nonce?以太坊中的nonce有两个意义:1.工作量证明:为了证明工作量的无意义的值,这是采矿的本质,这个值将决定采矿的难度,2.账户的随机数:在一个账户中的防止多重交易的用途。例如一个交易从A到B20个币,可能从A到B发送多次。为了防止交易的重播攻击,每笔交易必须有一个nonce随机数,针对每一个账户nonce都是从0开始,当nonce为0的交易处理完之后,才会处理nonce为1的...

1 什么是nonce?

以太坊中的nonce有两个意义:

1.工作量证明:为了证明工作量的无意义的值,这是采矿的本质,这个值将决定采矿的难度,

2.账户的随机数:在一个账户中的防止多重交易的用途。例如一个交易从A到B 20个币,可能从A到B发送多次。

为了防止交易的重播攻击,每笔交易必须有一个nonce随机数,针对每一个账户nonce都是从0开始,当nonce为0的交易处理完之后,才会处理nonce为1的交易,并依次加1的交易才会被处理。以下是nonce使用的几条规则:

  • 当nonce太小,交易会被直接拒绝。
  • 当nonce太大,交易会一直处于队列之中,这也就是导致我们上面描述的问题的原因;
  • 当发送一个比较大的nonce值,然后补齐开始nonce到那个值之间的nonce,那么交易依旧可以被执行。
  • 当交易处于queue中时停止geth客户端,那么交易queue中的交易会被清除掉。

2 以太坊源码中关于Nonce的解读

以太坊在geth的console端使用eth.sendTransaction({from:addr1,to:addr2,value:money})的形式来转账ether的时候,会通过RPC调用到/ethapi/api.go中的函数SendTransaction():

// SendTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.To. If the given passwd isn't
// able to decrypt the key it fails.
func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
	if args.Nonce == nil {
		// Hold the addresse's mutex around signing to prevent concurrent assignment of
		// the same nonce to multiple accounts.
		s.nonceLock.LockAddr(args.From)
		defer s.nonceLock.UnlockAddr(args.From)
	}
	signed, err := s.signTransaction(ctx, args, passwd)
	if err != nil {
		return common.Hash{}, err
	}
	return submitTransaction(ctx, s.b, signed)
}
只听到从架构师办公室传来架构君的声音:
不惜两钟输一斛,尚赢糠核饱儿郎。有谁来对上联或下联?

sendTransaction()继续调用submitTransaction()函数来执行交易:

此代码由Java架构师必看网-架构君整理
// submitTransaction is a helper function that submits tx to txPool and logs a message. func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } if tx.To() == nil { signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) from, err := types.Sender(signer, tx) if err != nil { return common.Hash{}, err } addr := crypto.CreateAddress(from, tx.Nonce()) log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex()) } else { log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To()) } return tx.Hash(), nil }

submitTransaction()在sendTx()函数中发送交易。然后在判断交易地址是否为空,为空的则是创建合约的交易。

追踪sendTx()函数到/core/tx_pool.go中的addTx()函数,原来是通过addTx()将交易添加到交易池。

func (pool *TxPool) addTx(tx *types.Transaction, local bool) error {
	pool.mu.Lock()
	defer pool.mu.Unlock()

	// Try to inject the transaction and update any state
	replace, err := pool.add(tx, local)
	if err != nil {
		return err
	}
	// If we added a new transaction, run promotion checks and return
	if !replace {
		from, _ := types.Sender(pool.signer, tx) // already validated
		pool.promoteExecutables([]common.Address{from})
	}
	return nil
}

addTx()首先调用add()将交易添加到交易池pool,如果成功,则调用promoteExecutables()函数去执行交易。promoteExecutables()函数关键代码是:

此代码由Java架构师必看网-架构君整理
// Gather all executable transactions and promote them for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {     hash := tx.Hash()     log.Trace("Promoting queued transaction", "hash", hash)     pool.promoteTx(addr, hash, tx)     log.Info("lzj log:","hash",hash,"Nonce",pool.pendingState.GetNonce(addr)) }

这段代码收集所有可执行交易并且执行它们。pool.pendingState.GetNonce(addr)得到和地址addr相关的nonce范围N,N等于len(addr.nonces)) + addr.nstart,其实等于该地址已经发送成功的交易个数。list.Ready()函数将返回一个交易集合,这个集合中的所有交易都要求满足nonce不大于N。只有Nonce满足要求的交易才会被处理,就是通过pool.promoteTx(addr, hash, tx)执行交易。

3 Web3j等RPC方式设置Nonce的正确方式

一般的通过Web3j发起以太坊交易的方法是:

Transaction.createEtherTransaction(from,nonce,gasPrice,gasLimit,to,value);

其中的nonce得到方式是:

EthGetTransactionCount ethGetTransactionCount = ethClient.ethGetTransactionCount(
                from, DefaultBlockParameterName.LATEST).sendAsync().get();
BigInteger nonce = ethGetTransactionCount.getTransactionCount();

通过取得当前该账户发起过的交易次数,并设其为当前交易的Nonce值。

如果要在for循环里同时发起多笔交易,因为当前交易尚没有被处理成功,所以通过上述方法得到的Nonce将会与上一次交易的Nonce相同,如果这笔交易的Gas比上次交易大,则会替换上次交易,否则将会报如下错误:

transaction failed,info:replacement transaction underpriced

解决这个问题的方法是自己维护Nonce,同一个账户连续发送俩笔交易时,通过递增Nonce来防止Nonce重复。

此种方案也有限制条件。第一,由于nonce统一进行维护,那么这个地址必须是内部地址,而且发起交易必须通过统一维护的nonce作为出口,否则在其他地方发起交易,原有维护的nonce将会出现混乱。第二,一旦已经发出的交易发生异常,异常交易的nonce未被使用,那么异常交易的nonce需要重新被使用之后它后面的nonce才会生效。

本文来源阿卡司机,由架构君转载发布,观点不代表Java架构师必看的立场,转载请标明来源出处:https://javajgs.com/archives/222380
0
 

发表评论