如何玩转Bycoin钱包?这篇文章告诉你
比原链细心的用户可能发现Bycoin钱包在高级设置下面增加了几个新的功能——钱包余额刷新工具,UTXO合并工具,如下图:
这些小工具可以一键解决用户使用过程中的一些小问题,那么这些小工具有什么作用呢?
钱包余额刷新工具
钱包余额刷新工具可以强制节点再次更新用户账户的余额信息,解决合约转账或者余额未及时同步的问题。
外部账户和合约账户
我们知道以太坊中有两类账户——外部账户和合约账户
外部账户:该类账户被公钥-私钥对控制(人类),外部账户的地址是由公钥决定的
合约账户:该类账户被存储在账户中的代码控制,合约账户的地址是在创建合约时确定的(这个地址由合约创建者的地址和该地址发出过的交易数量计算得到)
两种账户都可以实现对以太币的管理,包括持有以太币、查询余额、发送交易等。
交易和内部交易
一般用户转账的时候,都是使用外部账户对代币合约发起调用,调用转账的方法,最终发起转账的请求到以太坊节点。
那么什么是内部交易,内部交易就是合约之间的调用,通过合约状态的改变从而达成转账的目的
针对第一种普通转账,Bycoin可以很轻易检测到用户余额的改变,而内部交易较难检测到其内部的行为。所以通过钱包余额刷新工具来更新内部交易产生的交易而导致的余额变化。
钱包余额刷新工具做了什么?
钱包余额刷新工具会请求以太坊的节点,来重新查询用户的地址,获取内部交易产生的余额变动,并将查询结果记录到数据库中,从而将用户余额正确更新.
func (s *Service) SyncBalance(c *gin.Context) error {
address, err := formatETHAddress(c.Query("address"))
if err != nil {
return commonTypes.ErrAddressFormat
}
if err := s.DB.Slave().Model(orm.Address{}).Where("address=?", address).First(&orm.Address{}).Error; err != nil {
return commonTypes.ErrNotFoundAddress
}
ormAssets := make([]orm.Asset, 0)
if err := s.DB.Slave().Model(orm.Asset{}).Find(&ormAssets).Error; err != nil {
return commonTypes.ErrAssetID
}
blockHeight, err := s.node.GetBlockCount()
if err != nil {
return err
}
for _, asset := range ormAssets {
if asset.Asset == ETH {
if err := ethereum.UpdateBalance(s.DB.Master(), address, s.node, asset.Asset, asset.ID, int64(blockHeight)); err != nil {
return err
}
} else {
if err := ethereum.UpdateBalance(s.DB.Master(), address, s.node, asset.ContractAddress, asset.ID, int64(blockHeight)); err != nil {
return err
}
}
}
return nil
}
func UpdateBalance(db *gorm.DB, address string, node *Node, asset string, assetID uint64, blockHeight int64) error {
......
// UPDATE
} else {
// ERC20 balance
balanceBigInt, decimal, err := node.GetERC20Balance(address, asset)
if err != nil {
return err
}
......
}
addressInfo := orm.Address{Address: address}
if err := db.Select("id").Where("address = ?", address).Find(&addressInfo).Error; err != nil {
errors.Wrap(err, "err select address")
return nil
}
balance := &orm.Balance{
AddressID: addressInfo.ID,
AssetID: assetID,
Balance: value,
PendingBalance: pendingValue,
LastUpdateBlock: uint64(blockHeight),
}
return saveBalance(db, balance)
}
UTXO合并工具
很多朋友都遇到过UTXO太碎导致交易不成功的现象,Bycoin通过这款工具可以一键解决此类用户的问题。
什么是UTXO
UTXO是 Unspent Transaction Output的缩写,意思是未花费的输出,可以简单理解为还没有用掉的收款。UTXO 核心设计思路是:它记录交易事件,而不记录最终状态。要计算某个用户有多少币,就要对其钱包里所有的UTXO求和,得到结果就是他的持币数量。
比原链和比特币一样,也是采用UTXO模型。
为什么需要UTXO合并
我们看到UTXO模型中,任何一笔交易的输入都是前一笔交易的输出,然后当你的UTXO数量非常多时,你的交易会有很多的输入,从而整体的交易体积会变大,因为区块的容量有限制,为了防止有人恶意使用大量小的UTXO构建一笔体积非常大交易,从而出现堵塞区块的出现,比原链限制了输入UTXO的个数最大为20个,当你UTXO太多时,可能会出现交易不成功的现象,那么我们需要将UTXO合并工具,通过发送交易给自己,将小额的UTXO合并成大的UTXO。
UTXO合并工具做了什么?
UTXO合并工具首先判断是否有合并的余额,以及是否需要合并
balance, err := coin.CalcBalance(s.DB.Slave(), account, req.Asset, req.Confirmation)
if err != nil {
return nil, err
}
if balance.Cmp(big.NewInt(0).SetUint64(totalAmount)) < 0 {
return nil, errors.Wrap(types.ErrInsufficientBalance, fmt.Sprintf(" balance %s less %d", balance.String(), totalAmount))
}
然后会寻找该账户关联的所有的UTXO
utxoFinder := coin.NewUTXOFinderWithFindStrategy(s.DB.Slave(), ormCoin, account, builder.EstimateGas, builder.FindUTXOs)
if req.SpendAll {
optUTXOs, err = utxoFinder.PrepareAllUTXOs(req.Asset, req.Confirmation, s.Cfg.Coin.DesiredUtxoCount)
if err != nil {
return nil, errors.Wrap(err, "prepare all utxos")
}
}
if len(optUTXOs) == 0 {
optUTXOs, _, err = utxoFinder.PrepareUTXOs(req.Asset, totalAmount, req.Confirmation, s.Cfg.Coin.DesiredUtxoCount)
if err != nil {
return nil, errors.Wrap(err, "prepareUTXOs")
}
}
if !req.ForbidChainTx && len(optUTXOs) > s.Cfg.Coin.ChainTxMinUTXONum && (s.Cfg.Coin.ChainTxMergeGas == 0 || req.Asset == consensus.BTMAssetID.String()) {
chainUTXOs := optUTXOs
if !req.SpendAll {
chainUTXOs, err = utxoFinder.PrepareChainUTXOs(req.Asset, totalAmount, req.Confirmation, s.Cfg.Coin.DesiredUtxoCount, s.Cfg.Coin.ChainTxMinUTXONum, s.Cfg.Coin.ChainTxMergeGas)
if err != nil {
return nil, errors.Wrap(err, "prepare chain utxos")
}
最后通过链式交易进行合并。
chainTxs, utxos, err := builder.BuildChain(s.TxDecoder, chainUTXOs, req.Asset, s.NetParams, s.Cfg.Coin.ChainTxMinUTXONum, s.Cfg.Coin.ChainTxMergeGas)
if err != nil {
return nil, errors.New("build tx chain")
}
for _, chainTx := range chainTxs {
resp, err := s.createBuildPaymentResp(account, chainTx.OptUtxos, chainTx.Fee, chainTx.Tx, chainTx.SigningInstructions, "")
if err != nil {
return nil, err
}
result = append(result, resp)
}
optUTXOs = utxos
}
fee := builder.EstimateGas(len(optUTXOs), account.M(), account.N())
resp, err := s.buildPaymentWithUTXO(req, req.Asset, ormCoin, account, optUTXOs, fee, builder)
if err != nil {
return nil, errors.Wrap(err, "build payment")
}
return append(result, resp), nil
}