“打点创新营”主题分享:SV 开发中的那些坑

https://aaron67.cc/2020/01/14/dotcamp-presentation-20200104/

大家下午好!我是 Aaron。感谢哲明和打点钱包的邀请,让我分享一些开发过程中遇到的问题。

我想从“192”这个神奇的数字开始今天的内容:普通用户能发出来的、最小的一笔交易的大小,是 192 字节。

作为开发者,其实就是通过代码来操作交易与比特币系统交互。交易是比特币系统中最重要的部分,对交易细节的把握可以让你在写代码的过程中避免掉坑。所以我先讲些交易的细节。

从程序员的角度来说,一个交易长这个样子:开头是一个 4 字节的版本号,标记这笔交易的结构是什么版本;随后是一个 1 ~ 9 字节的可变长整数,标识这笔交易中输入的个数;后面跟着一个数组,多个交易输入按顺序排列;接着是一个可变长整数,标识交易有多少输出,后面跟着具体的交易输出;最后是一个 4 字节的 nLocktime,标记交易的时间锁。

对于交易输入的结构:开头是一个 32 字节的交易哈希和一个 4 字节的序号,表达交易链条,指示这笔交易输入来自之前哪笔交易的第几个输出;接着是一个可变长整数,表示解锁脚本的长度,后面跟着具体的解锁脚本;最后是 4 字节的 nSequence。对于交易输出的结构:开头是一个 8 字节整数,表示这笔交易输出(UTXO)的金额;接着是一个可变长整数,表示锁定脚本的长度,后面跟着具体的锁定脚本。这就是一笔交易的结构,很简单,比特币系统中所有的交易都是这个样子。

普通用户能接触到的交易基本都是 P2PKH(付款到公钥哈希)交易,作为开发者,操作的交易也基本全是 P2PKH。

所以我们可以细化一下交易的结构图,因为确定了交易类型,就等于确定了解锁脚本和锁定脚本的结构。对于 P2PKH 交易,解锁脚本分两部分,前面是 DER 格式的签名,后面跟着公钥,第一个 OP_72 表示签名的长度,后面的 OP_33 表示公钥有 33 字节;锁定脚本的结构也是确定的,有 25 字节,5 个操作码加上 20 字节的收款方公钥哈希。

所以普通用户能发出来的最小的交易,是只有一个输入和一个输出的 P2PKH 交易,自下而上,计算出来交易的大小是 192 字节。

为了验证,我发了一笔交易,有一个输入,有一个输出,交易的大小是 192 字节,手续费 1 聪每字节,最后我支付了 192 聪的费用,截图里你能看到实际情况确实如此。

介绍 192 这个数字的目的是什么呢?因为很多开发者在写代码时忽略了另一个数字,这个数字是“546”:交易输出的金额,不能小于 546 聪。我说一下它是怎么来的。我们都知道,节点在收到交易之后,要验证这笔交易,如果交易合法且交易是标准交易,节点才会把它放入自己的内存池(mempool),并向相邻的节点(peers)传播(relay)这笔交易。最早在做压测工具的时候,为了尽可能的省钱,我需要将每笔交易输出设置的尽可能小,那就 1 聪好了,但是交易被拒绝了。

翻源代码找原因,看到一个函数IsStandardTx,最后有一步函数调用,判断交易输出是不是 dust,就是我用红线标记的那行。

看下这个IsDust函数做了什么,一共就三四行代码却有很多注释,拿出注释细看一下。

我把比较重要的内容标记成了三部分。

  1. 如果你在花费一笔 UTXO 时,需要支付的手续费超过了这个 UTXO 面值的 1/3,那么这个 UTXO 就是 dust;
  2. 交易输出有 34 字节,把它作为交易输入花费掉需要 148 字节;
  3. 得出来交易输出最小金额的公式是 546 乘以最小的手续费单位。目前最少的手续费是 1 聪每字节,所以说最小的 UTXO 面值是 546 聪。

之前的交易结构图里有说到注释里的 148 和 34 都是怎么来的,列个式子计算一下即可。

很多开发者在构造交易时注意不到 546 的限制,一些底层库在处理交易找零时也没有特别处理。比如说我现在有一个 1000 聪的 UTXO,支付了 800 聪,找零 200 聪。这笔交易是合法的交易,但广播后节点会拒绝,也不会 relay 它,这会给开发者开来困扰。这张图是 BitSV 库内部的处理方法,你能看到,当交易剩余的找零小于 546 聪时,它会把这部分输出直接当成矿工费,以避开 dust 限制。当你在使用其他语言库写代码时,务必要注意这些细节。

接下来我想说说 UTXO 这种数据结构。UTXO 是构成交易的基本元素,作为开发者,这种数据结构很友好,但普通用户却难以理解它。基于余额设计财务系统是非常直观的,转账发生时更新余额字段的值即可,但要注意,更新共享变量需要加锁,否则可能会读到脏数据或计算出负余额,而加锁就意味着对并发操作并不友好,系统难以水平扩展。UTXO 结构是高并发友好的,多个 UTXO 之间毫无关系且彼此独立。老刘 Edward 之前有篇文章讲到还可以将 UTXO 看成一种文件共享锁,感兴趣的可以去读一读。

作为普通用户,当你在玩 BSVRUN 的时候一定见过这样的画面,玩着玩着你就不能继续了。

去年春节,当你使用打点钱包在群里给朋友发红包时,还会见过这样的错误,红包发着发着就发不出来了。这是现在比特币系统中的另一限制:未确认的交易祖先不能超过 25 个。举个例子,如果不考虑手续费,我向哲明支付了 1 BSV,这笔交易产生了一个 1 BSV 的 UTXO,现在是未确认的状态;哲明不需要等待交易确认,他可以立即将这 1 BSV 发送给晓峰;晓峰同样不需要等待,可以立即将收到的 1 BSV 支付给 AusLiu。现在,AusLiu 有了一个 1 BSV 的还未确认的 UTXO,而这个 UTXO 依赖的交易祖先,也都是未确认的。比特币系统允许用户花费未确认的 UTXO,但对这些 UTXO 未确认的交易祖先的个数有限制。如果你的用户会在应用里互相高频的发送交易,那你一定会碰到这个问题。如果我需要在 1 秒内发出 100 笔交易,不对 UTXO 做预处理就无法实现这个需求。

绕过这个限制的方法很直接,你需要预先拆分出足够多的 UTXO。

因为出块就意味着交易确认,所以拆分过程可以把出块当做触发器。起始是 1 个黄色的 UTXO,程序监听到网络出块,黄色的 UTXO 变成了已确认的状态,构造一笔交易,将它拆分成 400 个绿色的UTXO,然后等待,再次出块后,绿色的 UTXO 都变成了已确认,再构造交易,把每个绿色的 UTXO 拆成 400 个红色的 UTXO,这样一直拆下去,直到得到足够多的 UTXO。拆分得到的 UTXO 数量呈指数增长,效率够用。回到上面的问题,我只需要预先将 UTXO 拆成 100 份并等待 1 个区块确认即可。很多 BSV 应用都需要做频繁的小额支付,提前想到解决方案,用户体验会好很多。

前几天,一个朋友联系我说他的 Money Button 不能发送交易了,他没法支付放在账户里面的 BSV。

我说那你在别的钱包软件里恢复一下助记词,看看到底发生了什么。他恢复了之后说自己有一个地址里被加特林灌了几百个 700 聪的 UTXO。

我说那没事,合并一下就行了。在 ElectrumSV 里全选这些小额 UTXO,支付到一个新地址,把它们它合并成一个输出就可以了。

但他广播交易时遇到了错误,ElectrumSV 的服务器做了限制,会拒绝比较大的交易。最后我给他发了段代码解决了这个问题。

对于几百个 UTXO 的合并非常简单,但对于这样一个有三百多万次历史交易、三万个 UTXO 的地址来说,想使用现有钱包导入私钥做输出合并没任何可能性。如果你想花这个地址里的 BSV,只有写代码。前面我讲了拆分 UTXO,现在讲讲合并零碎 UTXO 的方案。

如果你还记得之前的这张图,这是一输入一输出 P2PKH 交易的结构。在合并时,我们可以将每 100 个 UTXO 合并成 1 个新的 UTXO,计算一下,每笔交易的手续费是 14844 聪。验证一下,新建一个交易,左边是签名前,有 100 个输入,1 个输出,显示交易大小确实是 14844 字节,需要 14844 聪的手续费。看起来不错,计算是正确的。然后对交易签名,签名后的交易是右边截图的样子。很奇怪,交易的大小发生了改变,签名之前是 14844 字节,签名之后变成 14793 字节,为什么?

难道说之前的计算有什么问题吗?不太确定,我又构建了一个新的一输入一输出的交易,发现它在签名前是 192 字节,签名后变成了 191 字节。这个又勾起我的好奇心,为什么会发生这样的情况。

所以我搜了些文章看,原因这里不展开说了。很明显交易大小的改变是因为签名的长度会变,未确认的交易没有实际的签名数据,只是会预留空间,所以其大小与理论计算值一样,而签名后因为实际的签名数据会变小,所以存在差值。这页里我放了两个参考链接,感兴趣的朋友可以之后看。

让我们回到之前 MB 的错误上来,顺着这个思路往下想,你会找到一个有趣的攻击点。

Money Button 的用户可以使用 paymail 地址收款。这本不是什么问题,但 MB 的 paymail 支持“用户ID号@moneybutton.com”格式。这意味着,我可以不需要事先知道 MB 用户具体的收款地址或 paymail 而向他们支付 BSV,也就是说,我可以按顺序依次向 ID 是 1、ID 是 2 … ID 是 10000 的 MB 用户支付大量的小额 UTXO,从而让他们无法再通过 MB 的网站操作自己的账户。整个攻击的成本非常低。

最后我想说说广播交易这块的坑,FastPayButton 的邱总和我在这里都曾遇到过问题。FPB 之前使用 BitIndex API,他们上线城市交易的那天下午,BitIndex 广播交易的 API 有问题,调用后会返回正确,与往常没任何区别,但实际上你的交易并没有真正被广播出去,网络中并不存在这些交易。所以他们最后发现交易了很多城市,却都没收用户的钱。

后来我们讨论了一下,觉得比较好的做法是不要直接认可服务商返回的广播结果,在一些非常关键的业务场景,多花一点时间去访问其他不同服务商的接口,确认交易是不是也进入了他们的内存池。如果是,再去做接下来的逻辑,让用户等待两三秒的时间,避免意外。

最后一点时间,说说我这一年来的感受。SV 的开发社区非常活跃,在座有一半以上的熟面孔,我们经常在全国各地的活动中碰到。当我遇到开发难题时,有经验的朋友能毫无保留的分享解决方案和思路,他们热情、可爱、平易近人,虽然我们之前从未谋面不认识彼此。下个月的 Genesis 升级会解锁被限制的脚本操作码,基于 BSV 也可以做出更多更好玩的应用。生态的建设,需要在座每一位的参与,需要更多的开发者加入进来。如果你有兴趣基于 BSV 做一些尝试,不要犹豫。

以上就是我今天的分享,谢谢大家!

aaron67-dotcamp-20200104.pdf

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 MATLAB ,您可以使用 `text()` 函数来打点标注。该函数的语法如下: ```matlab text(x, y, txt) ``` 其,`x` 和 `y` 是要标注的点的坐标,`txt` 是要显示的文本。例如,要在坐标点 (1,2) 处打点标注 "Point",可以使用以下代码: ```matlab x = 1; y = 2; txt = 'Point'; text(x, y, txt); ``` 此外,您还可以使用 `scatter()` 函数来在图形绘制点,并使用 `text()` 函数为每个点添加标注。例如,以下代码将在图形绘制一个点,并在该点处打点标注 "Point": ```matlab x = 1; y = 2; txt = 'Point'; scatter(x, y); text(x, y, txt); ``` ### 回答2: 在Matlab,我们可以使用plot函数来绘制曲线,并使用text函数来添加标注。 首先,使用plot函数绘制曲线。例如,我们可以使用以下代码绘制一条简单的曲线: x = 1:10; % x轴的值 y = [1 2 3 4 5 4 3 2 1 2]; % y轴的值 plot(x, y) 接下来,使用text函数为曲线上的某个点添加标注。例如,如果我们希望在坐标点(5, 4)上添加标注,可以使用以下代码: text(5, 4, 'Point', 'HorizontalAlignment', 'center') 上述代码,(5, 4)是标注的坐标点,'Point'是要显示的标注文本。'HorizontalAlignment'指定了文本的水平对齐方式,'center'表示文本水平居对齐。 我们也可以将text函数与get(gca,'YLim')和get(gca,'XLim')函数结合使用,以确保标注在图形范围内。例如,以下代码将在坐标点(x, y)周围添加标注: text(x, y, 'Point', 'HorizontalAlignment', 'center') xlim(get(gca,'XLim')) ylim(get(gca,'YLim')) 通过以上步骤,我们可以在Matlab实现打点标注。 ### 回答3: 在Matlab,可以使用`plot`函数打点并添加标注。具体步骤如下: 1. 首先,使用`plot`函数绘制散点图或曲线图。比如,我们可以使用以下代码绘制一个简单的曲线图: ```matlab x = 1:5; % x轴数据 y = [1 4 2 3 5]; % y轴数据 plot(x, y, 'o-'); % 绘制曲线图 ``` 2. 在绘图选取一个或多个点,使用`text`函数添加文本标注。`text`函数的第一个参数是要添加标注的点的x坐标,第二个参数是y坐标,第三个参数是要添加的文本。比如,我们可以使用以下代码在坐标(3,2)的点上添加标注: ```matlab text(3, 2, 'Point (3,2)'); % 在点(3,2)上添加标注 ``` 3. 如果需要在多个点添加标注,可以使用循环。比如,我们可以使用以下代码在多个点上添加标注: ```matlab x = 1:5; % x轴数据 y = [1 4 2 3 5]; % y轴数据 plot(x, y, 'o-'); % 绘制曲线图 for i = 1:length(x) text(x(i), y(i), ['Point (' num2str(x(i)) ',' num2str(y(i)) ')']); % 在每个点上添加标注 end ``` 以上就是在Matlab如何打点标注的简单示例。可以根据实际需求自定义坐标和标注内容,实现更复杂的标注操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值