单个交易可以打包的Msg数量()

在代码中,单个交易中包含的Msg数量存在两个限制:

  • 交易的大小: 即单个交易,最大的字节数
    • 代码中默认配置为 1048576 byte; 在 ${HOME}/.cetd/config/config.toml配置文件中进行配置,字段名为:max_tx_bytes
    • 细节请看如下描述
  • 交易中的签名数量
    • 代码中的默认配置为 7;在${HOME}/.cetd/config/genesis.json配置文件中进行配置;字段名为:tx_sig_limit

交易大小的限制

区块链上的每个交易,都会先进入交易池,然后才会打包进区块中,进行上链。

进交易池主要有两种途径:

  • 通过RPC,将交易广播给指定的节点
  • 通过P2P网络,在网路各个节点传输交易

所以,在每个交易进入交易池,都会进行如下检查:

func (mem *CListMempool) CheckTxWithInfo(tx types.Tx, cb func(*abci.Response), txInfo TxInfo) (err error) {
    ....
    // The size of the corresponding amino-encoded TxMessage
	// can't be larger than the maxMsgSize, otherwise we can't
	// relay it to peers.
	if max := calcMaxTxSize(mem.config.MaxMsgBytes); txSize > max {
		return ErrTxTooLarge{max, txSize}
	}
    ....

}

// calcMaxTxSize returns the max size of Tx
// account for amino overhead of TxMessage
func calcMaxTxSize(maxMsgSize int) int {
	return maxMsgSize - aminoOverheadForTxMessage
}

aminoOverheadForTxMessage = 8

由上述代码可以看到,当单个交易的大小超过mem.config.MaxMsgBytes时,该交易无法进入交易池。

签名数量的限制

这个地方,稍微有点复杂,需要先了解下标准交易的结构

// StdTx is a standard way to wrap a Msg with Fee and Signatures.
// NOTE: the first signature is the fee payer (Signatures must not be nil).
type StdTx struct {
	Msgs       []sdk.Msg      `json:"msg" yaml:"msg"`
	Fee        StdFee         `json:"fee" yaml:"fee"`
	Signatures []StdSignature `json:"signatures" yaml:"signatures"`
	Memo       string         `json:"memo" yaml:"memo"`
}

这个结构中,通过Signatures获取签名的数量; MsgsSignatures结合起来进行验签。

先说签名的数量

交易中签名数量即为 StdTx.Signatures字段的元素个数;在交易进入交易池,以及在节点收到区块时,都会每个交易进行签名验证.

  • 首先校验签名的个数是否超出限制
    • 使用ValidateSigCount进行签名数量的验证
  • 再验证每个签名是否正确
    • 使用stdTx.GetSigners()获取交易中签名者的数量(签名者的数量应该与签名的数量一致)
    • 使用获取到的stdSigssignerAddrs,进行签名的验证processSig.
      • 这里需要注意的是:交易中消息(Msg)的数量与签名的数量无关;
      • 一个Msg, 也可能需要多个签名
        • 可以看stdTx.GetSigners实现,交易的签名者是由msg.GetSigners合并组成的,当一个Msg自身需要n个签名者的时候,那这个Msg自身的确认,就需要n个签名
      • 多个Msg, 可能也只需要一个签名.
        • 可以看stdTx.GetSigners实现,当交易中所有的Msg返回的签名者为一个相同的值时,那这个交易只需要一个签名,即使它包含多个Msg.
func NewAnteHandler(ak AccountKeeper, supplyKeeper types.SupplyKeeper, sigGasConsumer SignatureVerificationGasConsumer) sdk.AnteHandler {
	return func(
		ctx sdk.Context, tx sdk.Tx, simulate bool,
	) (newCtx sdk.Context, res sdk.Result, abort bool) {
	
	    ....
	    // 验证签名的数量
	    if res := ValidateSigCount(stdTx, params); !res.IsOK() {
			return newCtx, res, true
		}
		
		// 验证签名
		signerAddrs := stdTx.GetSigners()
		stdSigs := stdTx.GetSignatures()
		for i := 0; i < len(stdSigs); i++ {
		    ....
		    
			// check signature, return account with incremented nonce
			signBytes := GetSignBytes(newCtx.ChainID(), stdTx, signerAccs[i], isGenesis)
			signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytes, simulate, params, sigGasConsumer)
			if !res.IsOK() {
				return newCtx, res, true
			}
			...
		}

	    ....
	
    }
}

验证签名:同一个交易中的所有签名都使用相同的待签名的序列化字节数组;GetSignBytes(); 然后又使用对应的公钥和签名进行验签;

ValidateSigCount验证签名的数量:从交易中的提取签名的数量;当签名的个数大于params.TxSigLimit值时,交易无效。

  • 由于params.TxSigLimit这个值是在genesis.json中进行配置的,所以,相同链上的所有节点,都保持相同的阈值,且运行途中不可以随意更改。
func ValidateSigCount(stdTx StdTx, params Params) sdk.Result {
	stdSigs := stdTx.GetSignatures()

	sigCount := 0
	for i := 0; i < len(stdSigs); i++ {
		sigCount += CountSubKeys(stdSigs[i].PubKey)
		if uint64(sigCount) > params.TxSigLimit {
			return sdk.ErrTooManySignatures(
				fmt.Sprintf("signatures: %d, limit: %d", sigCount, params.TxSigLimit),
			).Result()
		}
	}

	return sdk.Result{}
}

GetSigners获取交易中签名者的数量;有这里可以看出是由Msg来控制一个交易的签名者数量,也间接控制了签名的数量。
当多个Msg签名者一样时,只会返回作为一个签名者返回。

- 可以采用这种规则,进行如下活动:
    - 在一个交易中,包含多个签名者相同的Msg(因为此时);因为这样的交易,只需要一个签名(所有Msg都为相同的相同的签名者);只受交易大小的限制。
func (tx StdTx) GetSigners() []sdk.AccAddress {
	seen := map[string]bool{}
	var signers []sdk.AccAddress
	for _, msg := range tx.GetMsgs() {
		for _, addr := range msg.GetSigners() {
			if !seen[addr.String()] {
				signers = append(signers, addr)
				seen[addr.String()] = true
			}
		}
	}
	return signers
}

当一笔交易准备使用多个签名时;

  • 获取待签名的字节数组,所有签名使用相同的字节数组
  • 按照Msg中签名者的顺序,使用对应的私钥对这个字节数组(sha256后的内容)的进行签名
  • 将签名按顺序放入交易的StdSignature字段中。
1 Like