如何解析transaction manager

Android_Fragment解析(1)+transaction_141009 - android-fragment-transaction - ITkeyowrd
Android_Fragment解析(1)+transaction_141009
此次上传,两个文件,一个是基本的Fragment的排版, 一个应用了FragmentTransaction管理。详情见: http://blog.csdn.net/lmj/article/details/(Ps:本文,仅仅贴出第二个文件的部分代码,全部代码,请在全文最后,点击网盘链接下载)// MainActivity.javapackage com.yline.fragment_text_import android.app.Aimport android.app.FragmentMimport android.app.FragmentTimport android.os.Bimport android.view.Vimport android.view.View.OnClickLimport android.view.Wimport android.widget.LinearLpublic class MainActivity extends Activity implements OnClickListener{private LinearLayout mTabWprivate LinearLayout mTabFprivate ContentFragment mWprivate FriendFragment mF @Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);// 去掉 标题栏requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);// 初始化控件和声明事件mTabWeixin = (LinearLayout) findViewById(R.id.tab_bottom_weixin);mTabFriend = (LinearLayout) findViewById(R.id.tab_bottom_friend);mTabWeixin.setOnClickListener(this);mTabFriend.setOnClickListener(this);// 设置默认的FragmentsetDefaultFragment(); }private void setDefaultFragment(){FragmentManager fm = getFragmentManager();FragmentTransaction transaction = fm.beginTransaction();mWeixin = new ContentFragment();transaction.replace(R.id.id_content, mWeixin);<mit();}@Overridepublic void onClick(View v){FragmentManager fm = getFragmentManager();// 开启Fragment事务FragmentTransaction transaction = fm.beginTransaction();switch (v.getId()){case R.id.tab_bottom_weixin:if (mWeixin == null){mWeixin = new ContentFragment();}// 使用当前Fragment的布局替代id_content的控件transaction.replace(R.id.id_content, mWeixin);case R.id.tab_bottom_friend:if (mFriend == null){mFriend = new FriendFragment();}transaction.replace(R.id.id_content, mFriend);}// transaction.addToBackStack();// 事务提交<mit();}}// activity_main.xml&RelativeLayout xmlns:android=&/apk/res/android&xmlns:tools=&/tools&android:layout_width=&match_parent&android:layout_height=&match_parent& &&fragmentandroid:id=&@+id/id_fragment_title&android:name=&com.yline.fragment_text_two.TitleFragment&android:layout_width=&fill_parent&android:layout_height=&45dp& /&&FrameLayoutandroid:id=&@+id/id_content&android:layout_width=&fill_parent&android:layout_height=&fill_parent&android:layout_above=&@+id/id_ly_bottombar&android:layout_below=&@id/id_fragment_title& /&&includeandroid:id=&@+id/id_ly_bottombar&android:layout_width=&fill_parent&android:layout_height=&55dp&android:layout_alignParentBottom=&true&layout=&@layout/bottombar& /&&/RelativeLayout&注意:bottonbar布局中,设置的点击时间,对应的id应该为linearlayout类型效果图:文章文件下载:/s/1hqnCgew学习网站:http://blog.csdn.net/lmj/article/details/补充的知识内容:Fragment家族常用的APIFragment常用的三个类:android.app.Fragment 主要用于定义Fragmentandroid.app.FragmentManager 主要用于在Activity中操作Fragmentandroid.app.FragmentTransaction 保证一些列Fragment操作的原子性,熟悉事务这个词,一定能明白~a、获取FragmentManage的方式:getFragmentManager() // v4中,getSupportFragmentManagerb、主要的操作都是FragmentTransaction的方法1)FragmentTransaction transaction = fm.benginTransatcion();开启新一个事务2)transaction.add() 往Activity中添加一个Fragment3)transaction.remove() 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈(回退栈后面会详细说),这个Fragment实例将会被销毁。4)transaction.replace()使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体~5)transaction.hide()隐藏当前的Fragment,仅仅是设为不可见,并不会销毁6)transaction.show()显示之前隐藏的Fragment7)detach()将此Fragment从Activity中分离,会销毁其布局,但不会销毁该实例8)attach()将从Activity中分离的Fragment,重新关联到该Activity,重新创建其视图层次mit()//提交一个事务注意:常用Fragment的哥们,可能会经常遇到这样Activity状态不一致:State loss这样的错误。主要是因为:commit方法一定要在Activity.onSaveInstance()之前调用。上述,基本是操作Fragment的所有的方式了,在一个事务开启到提交可以进行多个的添加、移除、替换等操作。值得注意的是:如果你喜欢使用Fragment,一定要清楚这些方法,哪个会销毁视图,哪个会销毁实例,哪个仅仅只是隐藏,这样才能更好的使用它们。a、比如:我在FragmentA中的EditText填了一些数据,当切换到FragmentB时,如果希望会到A还能看到数据,则适合你的就是hide和show;也就是说,希望保留用户操作的面板,你可以使用hide和show,当然了不要使劲在那new实例,进行下非null判断。b、再比如:我不希望保留用户操作,你可以使用remove(),然后add();或者使用replace()这个和remove,add是相同的效果。c、remove和detach有一点细微的区别,在不考虑回退栈的情况下,remove会销毁整个Fragment实例,而detach则只是销毁其视图结构,实例并不会被销毁。那么二者怎么取舍使用呢?如果你的当前Activity一直存在,那么在不希望保留用户操作的时候,你可以优先使用detach。
此次上传,两个文件,一个是基本的Fragment的排版, 一个应用了FragmentTransaction管理。详情见: http://blog.csdn.net/lmj/article/details/(Ps:本文,仅仅贴出第二
相关阅读排行
相关内容推荐
请激活账号
为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。
您的注册邮箱:
如果您没有收到激活邮件,请注意检查垃圾箱。transaction
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和commit transaction或 rollback transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(commit transaction或 rollback transaction)之间执行的全体操作组成。SQL Server中事务语句开始或结束时transaction可简写为tran。君,已阅读到文档的结尾了呢~~
chapter2 transaction analysis交易分析
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
chapter2 transaction analysis交易分析
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer--144.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"contributes":[{"sourceColumn":{"lastUpdated":,"description":"","permission":"COLUMN_PUBLIC","memberId":6764027,"contributePermission":"COLUMN_PUBLIC","translatedCommentPermission":"all","canManage":true,"intro":"","urlToken":"c_","id":27284,"imagePath":"4b70deef7","slug":"c_","applyReason":"0","name":"金狗喵喵喵的区块链研习","title":"金狗喵喵喵的区块链研习","url":"/c_","commentPermission":"COLUMN_ALL_CAN_COMMENT","canPost":true,"created":,"state":"COLUMN_NORMAL","followers":295,"avatar":{"id":"4b70deef7","template":"/{id}_{size}.jpg"},"activateAuthorRequested":false,"following":false,"imageUrl":"/4b70deef7_l.jpg","articlesCount":9},"state":"accepted","targetPost":{"titleImage":"/v2-99bd6cf3c1ae_r.png","lastUpdated":,"imagePath":"v2-99bd6cf3c1ae.png","permission":"ARTICLE_PUBLIC","topics":[,11145],"summary":"这篇文章我断断续续写了呃···· 应该快三个星期了? 所以前后的风格可能差别相当大。真是十分的怠惰啊··· 最近实在是不够努力。用python重写bitcoin的项目也卡在网络编程部分(这方面真是我的软肋)这篇文章通篇都是文字-_-, 没有其他东西,这个样子给…","copyPermission":"ARTICLE_COPYABLE","translatedCommentPermission":"all","likes":0,"origAuthorId":0,"publishedTime":"T20:46:22+08:00","sourceUrl":"","urlToken":,"id":2709725,"withContent":false,"slug":,"bigTitleImage":true,"title":"bitcoin 源码解析 - 交易 Transaction(二) - 原理篇","url":"/p/","commentPermission":"ARTICLE_ALL_CAN_COMMENT","snapshotUrl":"","created":,"comments":0,"columnId":27284,"content":"","parentId":0,"state":"ARTICLE_PUBLISHED","imageUrl":"/v2-99bd6cf3c1ae_r.png","author":{"bio":"我超级懒的","isFollowing":false,"hash":"cb3b97fbae2e613fba0f76e7bd23fc76","uid":08,"isOrg":false,"slug":"jin-xiao-94-7","isFollowed":false,"description":"","name":"金晓","profileUrl":"/people/jin-xiao-94-7","avatar":{"id":"v2-a03f59f4f9d3ad3f3a4db6fc186c4c83","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"memberId":6764027,"excerptTitle":"","voteType":"ARTICLE_VOTE_CLEAR"},"id":610314}],"title":"bitcoin 源码解析 - 交易 Transaction(二) - 原理篇","author":"jin-xiao-94-7","content":"这篇文章我断断续续写了呃···· 应该快三个星期了?
所以前后的风格可能差别相当大。真是十分的怠惰啊··· 最近实在是不够努力。用python重写bitcoin的项目也卡在网络编程部分(这方面真是我的软肋)这篇文章通篇都是文字-_-, 没有其他东西,这个样子给读者会造成很大的压力吧····虽然题目所说的是原理,但是实际上一部分原理已经在前面几篇文章都有过一些零散的说明了,感觉写出来又有点重复。。所以最好先读过前面几篇可能看起来更好点。。等我把所有的东西都写完后应该会重新整理,然后重新写一份更可读的吧(这是个flag)该篇将会详细阐述 Bitcoin 的交易本质。同样,在本篇中不探讨区块,只讨论 Tx 在整个 bitcoin 系统中是如何运作的。本篇所用的术语承接于上一篇文章,并直接使用上一篇文章讨论的细节。在整个bitcoin 的源码中,尤为重要的文件只有main.cpp/.h (还有script.cpp/.h)这个文件,其他部分都属于附属功能。在main.cpp/.h文件中,包含了在后续版本中称为“core”的代码。我们都知道,bitcoin 系统中有2个核心概念,Tx和 Block。但是这两个概念联系的相当紧密,甚至难以分割。但是当我们单纯的说Tx的时候,下文会回避一些和Block相关的说明。在bitcoin 系统中,所谓的交易应该包含2个过程:产生交易-&交易被(矿工)验证我们可以看到在这个流程中,关联的人应该有3个:转账人,收款人,矿工(们)接下来我们分别介绍其中运作的原理,并通过这两个流程作为切入点,展开我对bitcoin交易的理解。Transaction 的产生现在我们概要性的描述一个交易产生的流程:A 想要转账 1 btc 给 B,那么流程如下(这部分代码实现位于 main.cpp CreateTransaction() 这个函数中):B 首先通过某种途径告诉了A自己的比特币地址(关于地址的介绍在其他文章中会详细描述)然后A根据B给出的地址,并且附加上一些其他信息(非必要) 产生了scriptPubkey,这个scriptPubkey 就是前一篇文中所提到的“锁”。然后,根据要转账的金额 value, A就开始产生一个交易了:首先A检查属于自己的所有CWalletTx, 实际上检查的是 mapWallet 这个全局变量:WalletTx是一般指代Out指向自己的Tx,而Out指向自己实际是在说,这个Out的 scriptPubkey 脚本中其中的地址是由自己本地所持有的公钥生成的。也就是说自己持有的所有的公钥对应的地址,只要Out的scriptPubkey的地址在自己的公钥地址库里,那么这个Tx就是 IsMine() 的
(不过位于WalletTx的还包含自己提起的Tx)通过一定的方式随机选择Out,直到被选择的Out所包含的Value之和大于等于需要转账的Value。在bitcoin中,我们把每个 Out 能够提供的 value 称为 credit然后根据选择的Out所在的Tx开始创建Tx首先根据 B 提供的地址生成的 scriptPubkey 和 转账的 value 生成一个 TxOut 填充到 新创建交易的 vOut[0] 中(在上文中已经说到,一个交易是由 n 个 TxIn 和 n 个 TxOut 构成,而这里表示 代表真正转账的Out 填充到 第 0 个 TxOut 中)被选择的Out构成的credit 之和如果是大于 需要转账的value,那么就意味着这笔交易结束后必须会有找零。由于bitcoin采用了 “UTXO” 机制,所以被选择的所有 Out 应该全部被“花费”掉,但是此时被选择的Out之和是大于需要转账的value的,所以必定存在差值。在这里,bitcoin 就是再创建了一个 Out,但是这个新建的Out的 scriptPubkey 使用的是自己的bitcoin地址(这个地址在0.1源码中是从被选择的Out中选择第一个Out提取出自己的bitcoin地址(因为这些Out都是指向自己的,既然指向自己,那么肯定是自己能控制的bitcoin地址),当然也可以选择创建一个新的密钥生成地址),而这个新建的Out的 value 就是
credit之和 减去 要转账的value, 也就是找零。之后就把这个新创立的Out填充到下一个TxOut的list中。(就是 vOut[1])然后就根据被选择的所有Out来创立这个 Tx 的TxIn list(原因见上文)。这里就是简单的使用这个TxOut所在的Tx的hash 和这个Out位于这个Tx的index(第几个)作为参数构建 TxIn,并且把这个 TxIn 添加到 TxIn list 当中。构建完TxIn List 后就需要生对这些 TxIn 的scriptSig (也就是提供能操作这些Out的钥匙,见上文),这里是如何完成签名的在之后关于script的文章会详细描述,总之这里可以先理解为 因为这些TxIn 是由 指向自己的 TxOut 构成的,这些TxOut 的scriptPubkey 是由自己所持有的公钥生成的地址构成,所以自己可以提供这些公钥对应的私钥来生成签名(Solver函数),这些签名就是散文提到过的“钥匙”(但是请注意,这里生成的签名到底能不能用,这个在创建这个Tx的时候别人是管不着的,这就是bitcoin精彩的地方。而维护这些签名是不是合法的,就是依靠广大的矿工,这在下一个流程中会描述。)此时一个交易实际上就被创立好了,根据bitcoin的规则,这个Tx的交易费用实际上是由这个Tx的大小(大小很大部分是由参与的TxIn 以及脚本的大小组成)决定的,所以这里要重新验证附加上交易费后有没有超过自己能提供的credit之和,如果超过了,那么只能把附加上交易费的总费用重新作为转账的value,并跳到第一步重新开始计算。在一笔交易真正成立之后,之后是一些扫尾工作,例如填充构成这个交易应该有的前置交易(AddSupportingTransactions),修改本地的一些存储信息(CommitTransactionSpent),在修改本地的存储信息中有一点很关键,就是标记该交易是已被花费过的。注意这里的标记是和CWalletTx相绑定的,并且标记的是当前的这个新产生的交易的TxIn所关联的交易。因为我们一般都认为在一个交易中一个参与者只应该提供一个地址,所以对于这个交易者来说,CWalletTx的fSpend标记可以代表这个交易对于该交易者的Out有没有有被花费(也就是说fSpend是针对该交易者的),之后在检索的时候可以节省很多。Transaction 的验证现在描述一个Tx的验证过程。请注意,对于 Tx 的验证重点并不是交易的发起人对Tx进行验证,而是需要广大的矿工对该笔Tx进行验证,也就是我们说的狭义上的关于Tx的共识。本人对Tx的验证并没有什么用,只有整个系统中的大部分人都认同这个Tx的时候,这个Tx才算是成立。(准确的说应该是当Tx打包入Block,这个Block被广播后大多数节点认同他并添加到最长链上那么这个Tx才算是真正的成立,但是本文先不讨论block)。但是请注意,从这部分开始,在讨论的过程中请认清,对于发起交易的交易者A,和对这笔交易认证的广大矿工,分别是依据从哪里获得的信息进行验证的。好我们继续上一节假设的场景,A转账了1 btc 给B,A 在检查了自己过去的Tx后依据自己保存的信息并认为这些信息合法后,创建了一个新的Tx,并将其通过某种方式进行广播(该部分会在网络相关的文中进行描述)。但矿工收到了这个Tx那么验证就可以开始了(接下来我们省略的主语都是矿工):验证的部分位于 CTransaction::AcceptTransaction() 函数中。首先先明确一点,在bitcoin系统中,无论查询什么东西,都是基于这个东西的hash,也就是说我们一般认为一个hash是这个对象(实例,实体)的索引id首先根据收到的信息的hash查询自己的Tx的MemoryPool(是mapTransaction这个全局变量)和本地存储看是否已经存在了这个Tx,如果已经存在了那么就跳过认证过程。检查待认证交易的所有TxIn持有的Tx的指针(概念上的指针,这里说的是COutPoint,就是这个TxIn是来自哪个Tx的),如果这个指针已经在自己的一个缓存中(mapNextTx,使用COutPoint作为key,value是这个TxIn(之前已经出现过的)被包含的那个Tx(代表已经有Tx或收到同一个Tx)),先检查已在缓存的Tx和当前收到的这个Tx哪一个“更新”,更新的话就保留,否则就退出验证检查收到的这个交易的TxIn提供的信息是否能和自己本地存储的已有的Tx信息匹配:如果是coinbase则以下步骤全部跳过TxIn对应的Tx(TxIndex)是否在本地存在简单检查TxIn持有的prevout 和 Tx 对应的 out的基本信息是否相同检查 TxIn 提供的 signature script 是否符合 存储在本地的Tx对应的TxOut的 public script检查 TxIn 对应的 Tx 存储在本地的 TxIndex
的对应的Out的部分(vSpent[prevout.n]) 是否是已被标记过的(标记过就是代表这个out已经被花费了,注意这里的已花费和CWalletTx的spent有点不一样),如果是未标记过则这个 TxIn 是合法的,并在下面进行标记,若是已被标记过的那就是错误的。如果以上都是正确的,这里不进行存储,存储放在区块打包部分至此这个交易已被验证通过将交易放入内存池里等待打包进行一些扫尾工作。接下来这个Tx就存在于该矿工的内存池中了,这个矿工 可能会义务的 帮你把这个验证正确的交易广播给更多的人,其他矿工接收到也会进行在本地进行相同的验证流程并可能广播给更多的人。之后就等待这个矿工把这个交易打包进入区块,当这个区块被大部分矿工承认后这个交易就生效了。简单分析上文的那两部分中我们详细的介绍了Tx的产生和验证,实际上真正的流程比上述复杂的多,有很多分支的处理。。。但是即便如此,上面所述若不对照相应的代码,估计任何初始者看了都是一头雾水。我只是把我认为比较重要的部分用黑体标识出来。其中我用黑体标识出的部分重点在于突出一个Tx到底是怎么在一个分布式环境中,即便各个节点是互不信任的,仍然可以接受别人产生的交易。在上面的那两个过程中:Tx 的产生 对应的是
交易发起者Tx 的验证 对应的是
承认交易的过程。若按照传统的流程(网上银行模型),这两个过程应该如下:ps(关于中央铸币节点的概念参考比特币白皮书的文章)交易发起者向中央铸币节点(如银行)获取自己的账号里的余额(非必须)-&交易发起者获得交易接受者的相关信息(如对方的银行卡号)-&交易发起者将转账金额和对方的信息告诉中央铸币节点中央铸币节点获取发起者的信息-&检查发起者的账户余额-&判断转账金额是否在余额之下-&销毁发起者账户需要转账的金额(减少发起者账户的资产)-&铸造给接收者账户接受转账的金额(增加接收者账户相同的资产)-&销毁和铸造构成了一个“事务处理” 而在bitcoin的转账的整体流程中,这两个过程是不一样的,其中的交易发起者和交易接收者的角度是一致的,但是验证交易的角色由原来的“中央铸币节点”转变成为了“所有的矿工及接受包含这个Tx的区块的所有人”交易发起者检查自己的本地存储的信息(Wallet)获得自己能够使用的总资产(非必须)-&交易发起者获得交易接收者的相关信息(比特币地址)-&经过本地的检查后(非必须)产生一个交易Tx并进行广播矿工(们)收到交易-& 根据自己的本地存储信息(所有的TxIndex)用于校验收到信息的 TxIn 对应的历史交易信息是否吻合,最主要包含两方面TxIn 对应的 Tx 的 Out 是否是 未被花费过的 (UTXO)TxIn 对应的 Tx 的 Out 的
pubkey_script
是否能被 TxIn 提供的 sig_script 所验证成功-& 若校验成功则放入内存池等待打包进区块====-& 当打包进入区块后,修改自己的本地存储(TxIndex)
把这个区块广播给更多的人==== (区块相关部分,以后会详细讲解)-&(其他矿工)若收到的区块是最长链-&对这个区块进行验证-&验证成功就保存下来(别人的历史记录)-&剔除在内存池中相关的Tx-&中断自己的打包区块过程-&重新打包剩下内存池中的Tx其中最后一点提一下:如果一个矿工内存池中的Tx不多,或者他有选择的接受一些Tx而剔除一些Tx(比如给的交易费不够高),如果他很强大,能够一直获得打包区块的记账权,那么很可能有一些交易就永远都无法被打包,即便其他矿工打包了你的交易,但是只要这个这些其他矿工抢不过这个很厉害的矿工,那么你的交易仍然是无法生效的,因为“没有被记录进历史记录当中”,你想根据这个交易产生剩下的交易也是不行的。总结经过上面的分析,从传统的方式和bitcoin的方式的比较可以得出,至少在我的观点中,最大的区别在于:校验历史记录的这个过程被转移到了哪里因为在保障交易流程的正确性中,关键点在于一个交易能够成功,是要依托于以前的交易是否是正确的,如:在传统交易中,要检查用户的账户余额是满足转账金额的在bitcoin中,要检查Tx使用的TxIn 是否是已被花费过及是否是这个交易的发起者能够控制的(pubkey_script, sig_script的配对)而传统交易和 bitcoin 是有相当显著的区别的:传统交易必须依托于 中心节点 ,绝对不可能绕过中心产生交易,否则这笔交易是不会被承认的bitcoin 交易的校验历史记录 完全依托于个人存储的历史记录 ,毫无例外,无论是个人还是矿工这就回到在分析章节提出的问题:一个Tx到底是怎么在一个分布式环境中,即便各个节点是互不信任的,仍然可以接受别人产生的交易。这个问题的答案很简单:因为节点是互不信任的,所以他们信任的是自己,节点只相信自己的历史记录,只会根据自己的历史记录做出判断。那么问题就来了:自己的历史记录到底是对还是错呢?(这里的对错是相对的含义,应该说是不是和大部分人一致)这个问题就涉及到了bitcoin的最高原则了,只认同工作量最长链为唯一公认的bitcoin链。那么剩下的问题就会在block的部分继续进行分析。","updated":"T12:46:22.000Z","canComment":false,"commentPermission":"anyone","commentCount":6,"collapsedCount":0,"likeCount":1,"state":"published","isLiked":false,"slug":"","lastestTipjarors":[{"isFollowed":false,"name":"一万个兵","headline":"","avatarUrl":"/da8e974dc_s.jpg","isFollowing":false,"type":"people","slug":"yiwangebing","bio":"程序员","hash":"a7d0e9ae38fbaba5a412","uid":860300,"isOrg":false,"description":"","profileUrl":"/people/yiwangebing","avatar":{"id":"da8e974dc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"isTitleImageFullScreen":true,"rating":"none","titleImage":"/v2-99bd6cf3c1ae_r.png","links":{"comments":"/api/posts//comments"},"reviewers":[],"topics":[{"url":"/topic/","id":"","name":"比特币 (Bitcoin)"},{"url":"/topic/","id":"","name":"区块链(Blockchain)"},{"url":"/topic/","id":"","name":"同济大学"}],"adminClosedComment":false,"titleImageSize":{"width":1400,"height":975},"href":"/api/posts/","excerptTitle":"","column":{"slug":"c_","name":"金狗喵喵喵的区块链研习"},"tipjarState":"activated","tipjarTagLine":"拯救吃土少年!","sourceUrl":"","pageCommentsCount":6,"tipjarorCount":1,"annotationAction":[],"hasPublishingDraft":false,"snapshotUrl":"","publishedTime":"T20:46:22+08:00","url":"/p/","lastestLikers":[{"bio":"走着走着被吃了","isFollowing":false,"hash":"c81bbefbb3","uid":76,"isOrg":false,"slug":"ray_coming","isFollowed":false,"description":"","name":"那年的烟水","profileUrl":"/people/ray_coming","avatar":{"id":"v2-d3da8e15a3f77fd34359c2","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"summary":"这篇文章我断断续续写了呃···· 应该快三个星期了? 所以前后的风格可能差别相当大。真是十分的怠惰啊··· 最近实在是不够努力。用python重写bitcoin的项目也卡在网络编程部分(这方面真是我的软肋)这篇文章通篇都是文字-_-, 没有其他东西,这个样子给…","reviewingCommentsCount":0,"meta":{"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-5d17e02b1db832cea284_r.png","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"区块链(Blockchain)"},{"url":"/topic/","id":"","name":"比特币 (Bitcoin)"},{"url":"/topic/","id":"","name":"同济大学"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"我超级懒的","isFollowing":false,"hash":"cb3b97fbae2e613fba0f76e7bd23fc76","uid":08,"isOrg":false,"slug":"jin-xiao-94-7","isFollowed":false,"description":"","name":"金晓","profileUrl":"/people/jin-xiao-94-7","avatar":{"id":"v2-a03f59f4f9d3ad3f3a4db6fc186c4c83","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"c_","name":"金狗喵喵喵的区块链研习"},"content":"比特币中的交易可谓是比特币的最核心部分。比特币由交易产生,而区块就是用来存储交易的。所以,交易是比特币存在的载体,同时也是比特币中最复杂的部分。交易的运作层层相扣,各个部分缺一不可,十分严密,由此体现出了中本聪高超的设计技巧。接下来将会花费多个章节逐步介绍bitcoin中的交易比特币或者类似的分布式系统在设计的时候会有一个和普通设计中有极大区别的地方:分布式中的每个节点既是 client 也是 server。所以在分布式系统的设计中,使用类来描述对象的时候,有时就要分清哪些情况下这个类是作为client使用的,哪些时候是作为server收到client的这个类使用。因为client和server运行的是同一套代码,而在实际运作过程中如果按照C/S的这种模型(其实不应该这样看待,应该转换自己的思维变为设计p2p节点的思维)来看待,就会看到C产出的 Tx/Block 和 S 收到的 Tx/Block。所以这时候就需要分清类中的属性哪些是属于client/server都等价对待的属性,哪些属性是分情况使用的。否则在看源码的过程中就很容易迷失自己的定位。本章介绍交易在bitcoin源码当中的总体概况本文只介绍 bitcoin 的 交易 在源码中是由哪几个类组合得到的,做个总体的介绍。而对于交易的原理,我们放在下一篇文章做详细介绍。本文会提前出现一些bitcoin的概念,但是目前并不需要知道指代什么,只需要先明白有这样的东西就足够了。如上图的类图所示,这个UML图中包含了bitcoin交易相关的所有关键类。CTransaction在图中,最为核心的类的是 CTransaction 这个类就是我们常说的bitcoin的 “交易” (一般称为 Tx, 后文也会沿用这种说法)其实对于这个 Tx 类,这里只是一个壳,本身这个类并没有什么作用。这个类起作用的是vector&CTxIn&\nvector&CTxOut&\n这两个关键成员变量。这两个成员变量分别代表着比特币交易的 “收入” 与 “支出”。比特币的交易并不是记录账户形的数据变化(比如我们采用银行的模型来描述 A 向 B 转账100元,那么银行在记录这个转账的过程中会出现3个记录,这三个记录连成了一个 Transaction (事务)过程:A 的账户减少100元,记录的id为 tid1,B的账户加上100元,记录的id为 tid2,一笔转账记录记录了tid1向tid2转账了100元,成为A账户减少与B账户增加的“关系连接”。),而是日志形:比特币的Tx 只记录A 向 B 转账的这个“关系连接”,这条日志记录只包含了 A 向 B 转账了 100 元这条信息。而这里的 in 就是记录着 从 ‘’谁“ 来(目前先简单的这样看,实际完全不止这样,后文会慢慢重新解释), out 就是转给了谁,而转账了多少钱是包含在 out 中的。在中本聪的命名风格是使用一个前缀代表这个属性的类型,如果是flag还会加上一个f。所以这里的 vin/vout 就是指代in 和 out 都是 vector 类型,所以这里我们可以看到,一个 Tx 的 in/out 是可以有多个的。在后文中,我们称呼 in 为 TxIn,out 为 TxOut (注意这里把 in out 比作两个人是完全不恰当的,之后会重新描述)而这两个类的另外两个属性int nVersion;\nint nTimeLock;\n前者显然是用来控制版本的(这个涉及到另一个区块链系统的核心缺陷问题 --
分叉 , 本系列可能暂时不会对这方面做出分析)而后者在 bitcoin v0.1 源码中并没有起到什么作用。但是这个属性在今后的 bitcoin 版本中提供了一个转账过程中能够约定时间的能力,因为这个版本不涉及就不进行分析描述了(从这里也可以看出中本聪的前瞻性)CTxIn / CTxOut从这步起,我们直接抛弃 ”两个人之间进行交易“这样的概念,直接认为在比特币的交易系统中是不具备”所有人“这样概念(这样肯定很奇怪因为都没有所有人了比特币还有什么意义,但之后会解释),而只是把 ”交易“ 看作 ”比特币流“ 的中转的中转节点,就像水流分叉合并的那些节点一样:典型的 bitcoin 交易链:(from )水流流量分叉图:而每个交易就是一个中转(分叉)节点,而每个交易的 in / out 就是 这个中转(分叉)节点的流入和流出。bitcoin 有一个相当相当重要的规定就是 每个 Tx 的 所有 In 进入了货币流必须在这个交易中全部流出去(流出去不代表成为其他Tx的In,而是必须要成为一个 TxOut。)举例来说:如果A 转账 100 给 B,但是现在A能控制的Out 有2个,一个是Out1是60,一个是Out2是50,那么A一检查自己的Out就会发现,60和50都不够100,那么就只能把 Out1 和 Out2 都作为当前要生产的 Tx 的 In。但是这种情况下,所有In的和就大于要支出的 100了。那么如果不付交易费的话,除去转账给 B 的 100 所对应的当前Tx的 Out,那么还会多出10。在bitcoin中就强行规定,这多出的10也要创建一个
Out 来锁住这10 块,以规定每笔交易的 In 和 Out 的总数都要相同。那么因为这 10 相当于我们通俗意义上的“找零”,所以这个 10 块的 Out 的锁当然就是 A 自己可以控制的锁,相当于这个Out指向了自己。所以这样我们可以看到,一个交易只含有一个输入和一个输出,那么这个交易并不是看作一个人转账到了另一个人身上,而是把比特币看作像流水一样的货币流,从某个地方流入到了这个交易的输入,由从这个交易的输出流到另一个地方去。那么接下来的问题就显而易见了--如何控制货币流的流动?答案你就是 CTxIn 和 CTxOut 的属性。我们来看下这两个类有些什么属性:class CTxIn{\npublic:\n
COutPoint prevout;\n
CScript scriptSig;\n
unsigned int nSequence;\n};
\nclass CTxOut{\npublic:\n
int64 nValue;\n
CScript scriptPubKey;\n};\n对于 CTxIn:COutPoint 这个类如其名,就是起到 Point 的作用,但是它的命名是 OutPoint, 最初接触的时肯定会感到迷惑。但这个名字确实起得十分正确:TxIn 虽然按照之前的分析可知它是 Tx 的 流入,而流入 Tx 必定来自于 另一个 Tx。 TxIn 只是 Tx 的一个属性,描述了 本 Tx 的 ”流入“ 的情况,但它本身也是个壳,而从哪个Tx流入的信息就是由 COutPoint 所记录。所以对于本 Tx 来说,TxIn 所含有的 ”从哪流入的“ 那个(上一个) Tx 对于本 Tx 来说 就是上一个 Tx 的 Out 的指向。而本 Tx 是不能持有上一个 Tx 的 out 的,所以就使用了 Point 指针来记录。nSequence 在 v0.1 中没有起到什么作用,也不会用来作校验,但是这个字段今后被作为了其他用途,而且成为了bitcoin的一个软分叉的最佳例子。对于 CTxOut:value 就是记录着”从这个出口会流出多少“的信息。简单的来说就是可以理解为通俗意义上的转账了。但是我们这里还是强调,首先理解bitcoin先抛开 支付交易 等概念,而是把 bitcoin 看成流动的水,而这里的 value 就是记录从这里会流出多少 bitcoin 的意思。显然一个 Tx 的所有 TxOut 的 value 的和 应该等于 所有 TxIn 流入的总和 (不考虑手续费,弱考虑手续费就是小于等于),否则这笔交易就应该认为是非法的(不能凭空多出钱来)。scriptSig / scriptPubkey那么没有介绍的 scriptSig 和 scriptPubkey 就是控制 ”凭什么从这里流出“ 的机制。这块绝对是 中本聪 创立 比特币的 又一大惊为天人的发明,这两个属性就是 导致今后著名的”智能合约“的雏形。今后将会花费详细的一篇文章仔细介绍。在这里我们先简单的介绍如下:在刚才的讨论中我们知道,比特币流从一个交易流动到了另一个交易,像这样一直传递下去。但是这样显然是不行的,因为没有人宣告这个”流“的归属。换句话说,我们在日常中使用100块钱进行交易,核心是因为这100块的纸币从一个人的手上流动到了另个一个人的手上。但是当你持有了这100块的纸币时,你就确认了这100块纸币流的归属。但是在比特币的体系中,请你直接抛弃这种思想,而是使用另一种方式来思考,而这种方式当你换个角度看的时候它就和你交易100块的纸币是相同的。这种思想那就是当我们重新审视交易的时候,我们发现货币流是从一个(多个)交易流向一个(多个)交易的过程。那么如果我们有独特的手段能够控制它为什么能流动,比如说在流出去的时候我们采取一个手段给这个出口 out 上一个锁,而当你想控制这个从这个出口出去的流的时候你就创造一把能打开这个锁的钥匙,作为下一个交易的 in 。也就是说我们连续的看2个交易的中间部分:前一个交易的 out 和 后一个交易的 in。如果我们能把 前一个 out 加一个锁,然后规定后一个 in 要能成立的条件是 in 附带的 钥匙 能够打开out锁。(维护为什么能开锁这个过程是由矿工挖矿保证,现在只说交易不说区块,所以可以先理解为这是天然存在的规则。)那么因为上锁和开锁是“个人”行为,但一个交易的out被上了一把只有一个特别的人才能打开的锁,那么就像本应该从这个out流出去的货币被“锁在了这个out”里面(注:这个上锁的过程不需要这个特别的人参与,这个特别的人只需要提供一点信息代表这把锁只能他打开就行(就是指地址))。那么这种行为就等价于只有这个特别的人才能“控制”这个Tx的Out,也就是只有这个特别的人才能 “占有” Out 里面被锁住的钱。虽然这个钱并不像现实生活中能够实实在在的把这100块钱拿到手上,而只能是通过 in/out 的锁控制 钱的流动。但是我们换个方向想,虽然我们只能提供这个“钥匙”,但是这个“钥匙和锁”能够控制 out 所含有的 货币流动,那么这个 in/out 的上锁开锁机制 是不是就相当于你占有了这笔钱?(因为虽然这个钱不是真正的在你手里(比如银行账户有个针对你的账户而bitcoin 系统没有),但是你可以控制一些 针对你的锁所锁定的钱的流动权利,那么就像水流的分叉点的出口只有你能开锁,虽然别人都看得到,但是因为别人不能开锁,那么别人也无可奈何,因为他们控制不了)。 所以我们可以看到,我们所谓的转账在bitcoin的系统当中,例如A转账100给B,那么就需要 B 向A 提供一些信息(bitcoin地址),这个信息不会暴露B的个人情况,但是可以表面B能够控制由这个信息产生的锁。随后 A 就可以创建一个交易,这个交易的out 就可以用B 提供的这个信息上了一把只有B能够控制的锁,然后这个交易的 in 就是 由 A 提供 A 能够控制的其他交易的Out 的 对应的钥匙。 如下图所示:好了,之上面这么大段的陈述过后,我们终于可以提出,CTxIn 和 CTxOut 的属性 scriptSig 和 scriptPubkey 就是刚在我们讨论中的 钥匙 和 锁。 scriptSig 就是用于对应签名的钥匙,而 scriptPubkey就是 B 提供了地址而生成的锁。而我们所说的实现钥匙和锁的功能,依靠的就是 这两个属性的类型 -& CScript从命名上可以看出,中本聪在设计之初就认为具有这样功能的东西应该是像”脚本“一样可以被”执行“,熟悉计算机的人当看到script 的命名就可以想象到 这个机制 是可以 ”被编程的”。而在bitcoin 的系统中也确实如此,bitcoin 提供了一系列的操作指令,可以让使用者自行编程。而验证的过程实际上就是执行了脚本。在此先不做过多描述,之后会有文章详细描述bitcoin的script系统。COutPoint这个类含有两个属性class COutPoint{\npublic:\n
uint256 hash;\n
unsigned int n;\n};\n按照上文的解释,我们可以得到这里的 hash 指代的就是 txin 所来自的那个Tx的 hash, 而n指代这个 in 是来自上一个交易的第 n 个 out, 如下图所示:CInPoint这个类在我们对 bitcoin 的讨论中不是很重要,这个类只出现在一个维护 COutPoint与CInPoint的 map 中。所以我们认为 CInPoint 和 COutPoint 是 键值对应关系。当我们确认了一个 COutPoint 的时候,我们可以假装把这个COutPoint看作是上一个 Tx 的 Out, 那么 这个 map 对应的 CInPoint 意思就是指代为 上一个 Tx的Out 指向的 下一个它持有的属性 class CInPoint{\npublic:\n
CTransaction* ptx;\n
unsigned int n;\n};\nCTransaction* 是一个 针对
COutPoint 这个Out 指向的 In 所在的那个交易。那么在COutPoint那个图的例子中就是指代当前的Tx 这个 Tx 的指针。而这里的 n 就是指代 这个 In 是当前的 Tx 的 第 n 个 In, 在上图中也同样是 0 (因为只有1个In)CScriptCScript 实际上就是一个 vector&unsigned char& 也就是说 Script 实际上就是一串 Bytes 流。只不过这个字节流 是可以被解析为 &指令& 或者 &指令& &数据& 这样的一个一个元信息。而一个 Script 就是这些元信息组成的字节流。所以 CScript 本身这个类 不重要,重要的是 Script 说代表的指令和数据,以及这些指令的组合关系以达到相应的效果。它的验证需要一个 VM 来执行(脚本),而执行(解析)指令的方式和指令的含义与规则就是VM的规则与实现CTxIndex / CDiskTxPos这两个类和bitcoin的协议也就没什么关系了,他们是用来Tx 在本地的存储与索引使用的。不过这里要注意,在bitcoin的源码中,CTxIndex 是很重要的一个类,它的存储,更新和删除控制这能否在本地存储中找到这个对应的 Tx 数据,以及标注这个Tx 是否被花费。class CTxIndex{\npublic:\n
CDiskTxPos pos;\n
vector&CDiskTxPos& vSpent;\n};\nclass CDiskTxPos{\npublic:\n
unsigned int nFile;\n
unsigned int nBlockPos;\n
unsigned int nTxPos;\n};\n在存储中,bitcoin 使用 Tx 的hash 为键,CTxIndex 为值进行存储。所以在拿到一个 CTransaction(或其子类)可以通过得到这个Tx的 hash 索引本地的存储得到这个Tx 所对应的 TxIndex。而 TxIndex 的属性 vSpent 就是一个相当重要的属性,因为这关系到一个 Tx 的Out 是否是 UTXO(Unspent Transaction Output)。由前文的讨论可知,那么一个 UTXO 就是一个被上锁了但是没有被开锁过的 Out。而这个TxIndex 的 vSpent 是一个 vector ,它和当前 Tx 的 vout 相对应。这里我们要强调,Tx 的产生和 确认 不是同一个决定的,是之前所讨论的 Client 和 Server。产生Tx 的称为 client ,接受确认这个Tx 合法的是 Server, Client 和 Server 存储的CTxIndex 是不会进行传输的!所以CTxIndex 在 C/S 上是分别生成的。那么我们使用 CTxIndex 的 vSpent 来标识这个 Out 是不是一个 UTXO 就相当重要了。因为 C / S 分别的存储都是根据自己的历史生成的,所以如果 Client 要欺骗别人, 是不能在 别人自己的验证中通过的。举例来说就是 A 产生了Tx 并告诉别人来确认这个 Tx 是合法的,但是 A 使用的一个 in 来自的一个 Out 是已经被花费过的,比如我们假设这个Out所在的Tx叫做 Tx_prev,这个Out是第3个Out,但是A不管,仍然使用了这个被花费过的Out。那么当别人收到这个Tx进行验证的时候,他们就检查自己Tx_prev所对应的自己的本地存储的 Tx_index_prev ,然后一检查 vSpent[3] 是否是null, 如果是null 那么就是合法的,如果不是Null,那么就代表这个Out已经被花费过了。可见这里的验证是和A的本地存储无关的,A不可能修改自己的本地存储来欺骗别人。因为传输的内容只有 Tx, 而 TxIndex 是各个节点根据收到的Tx或block 自己生成的。所以节点们一检查发现 vSpent[3] 不是个 null, 那么就会认为 A的那个 Tx 是非法的。而 CDiskTxPos 是代表这这个 Tx 在本地存储的位置。 在bitcoin 源码中,Tx 的存储是紧密的排列在文件当中的,而找到这个Tx就是首先找到存储的文件,再找到这个Tx在这个文件中的偏移。所以 nFile和 nTxPos 就分别代表着是哪一个文件和在文件中的偏移位置。nBlockPos 代表这个 Tx 在 Block 中的位置。CMerkleTx这个类是 Tx 的子类,这个类使用来在Block 中相关处理的时候用的。CMerkleTx 是矿工(前文指代的server)所保存Tx时相关的类它在原本的Tx的基础上添加了class CMerkleTx : public CTransaction{\npublic:\n
uint256 hashBlock;\n
vector&uint256& vMerkleBranch;\n
int nIndex;\n};\n3个属性,hashBlock代表着当前的 Tx 所在的Block 的hash(作为索引),vMerkleBranch是该Tx 在 merkle tree 中 所配对的所有hash值(这个配对的hash值在以后的文章会解释),这里是用来验证Tx 在block中的附加信息。index代表着该Tx在block中的位置。CWalletTx这个类是 CMerkleTx 的子类,实际上就是我们产生Tx以及和wallet相关的Tx。这里我们着重介绍Tx,和wallet的信息以及产生的过程就暂时不先在这里介绍。结尾以上就是对bitcoin中 Tx 相关的类的介绍。只能先说明 bitcoin 实现 Tx 的过程中使用了哪些类及类中的属性可能起到的作用。在下一篇文章中我将会介绍 Tx 的运作原理。而之后关于 Tx 的文章就是根据原理看源码是如何处理的。","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T20:17:49+08:00","url":"/p/","title":"bitcoin源码解析 - 交易 Transcation (一)","summary":"比特币中的交易可谓是比特币的最核心部分。比特币由交易产生,而区块就是用来存储交易的。所以,交易是比特币存在的载体,同时也是比特币中最复杂的部分。交易的运作层层相扣,各个部分缺一不可,十分严密,由此体现出了中本聪高超的设计技巧。接下来将会花…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":1,"likesCount":6},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/v2-916c9c7e3df14ba43aea_r.png","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"比特币 (Bitcoin)"},{"url":"/topic/","id":"","name":"区块链(Blockchain)"},{"url":"/topic/","id":"","name":"同济大学"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"我超级懒的","isFollowing":false,"hash":"cb3b97fbae2e613fba0f76e7bd23fc76","uid":08,"isOrg":false,"slug":"jin-xiao-94-7","isFollowed":false,"description":"","name":"金晓","profileUrl":"/people/jin-xiao-94-7","avatar":{"id":"v2-a03f59f4f9d3ad3f3a4db6fc186c4c83","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"content":"bitcoin 源码解析 - 交易 Transaction(三) - Script之前的章节已经比较粗略的解释了在Transaction体系当中的整体运作原理。接下来的章节会对这个体系进行分解,比较详细描述细节的构成。本章将要详细分析bitcoin交易中的交易脚本-script到底是什么东西。回顾和概要在前面的文章中提到,在bitcoin的体系中,一个交易是被发布到比特币的整体系统中的,而能够操控之前交易的的TxOut(被锁住的coin),是需要能够操控这个TxOut的人提供\"钥匙\"来控制。就像前文描述的,coin在整个系统中是像流水一样的在体系中进行流通,而coin在其中在分叉点的时候会有一个像 “锁” 的东西把coin锁在这个节点上。而根据这个锁产生了一个新的交易,继续流通被这个锁所锁住的coin,是需要提供一个\"钥匙\"的。所以这里的比喻:“锁”和“钥匙”就是比特币交易中的交易脚本Script其中 “锁” 对应着 scriptPubKey“钥匙”对应着 scriptSig但是单纯的把Script理解为“锁”和“钥匙”实在是太浅薄了。只能完成这点事情的并不能体现Script 的强大,也无法对后人创立“智能合约”有所启发。所以在我看来,比特币的Script实际上是:scriptPubKey 是上一个交易(out)提出的一个 “问题”而scriptSig 是我想使用上一个交易中钱,那么我就对你提出的这个问题提供我的“答案”因为公私钥的关系,所以如果scriptPubKey 提出的问题是公钥相关的问题,那么很明显,只有持有私钥的人才能回答这个问题,所以就简化为刚才的所说的“锁”和“钥匙”的关系。而另一方面,如何确认提供的“答案”就是能回答“问题”的呢?这就说明Script是需要被执行验证的,而且这个验证的过程只需要txin提供的scriptSig 和验证者自己从自己的记录中找到的txout的scriptPubKey ,而这个验证者就是广大的矿工们。整个系统精妙的地方就在于,scriptPubKey是验证者(矿工)各自独立持有的东西,其安全性由自己所保证的,而想要完成交易的人只需要提供scriptSig给广大验证者就行,不需要一些多余的上下文(可以理解为上下文由验证者自己持有,虽然大家都互不信任,但是对于最广大的人来说,这个上下文都是相同的)。另一个方面不太被大多数人所注意到的是:实际上刚才的模型简化为了“问题”和“答案”,但是这个“问题”可不是很容易提供的。这个“问题”应该满足2个方面的要求:问题的答案必须是十分明确的,唯一的,不能是个模糊的要求(这点在代码中就是“代码就是法律”的体现吧(笑),或许这就是智能合约无法完成真正人们所向往的替代所有合同执行的原因,因为合同虽然签订了,但是其中的内容其实很多是有讨价划价,钻空子的空间的)答案必须容易的被验证而不需要其他上下文环境。(这点就是这个问题提出的困难的地方,也就是这个问题要么正向很难,逆向很容易,要么验证需要提供其他的附加的上下文环境。)而公私密钥的模式其实是完美的符合了这2方面的要求的。那么有没有其他的问题呢?那是当然有的,比如我提出了一个数学问题,这个问题的解是唯一的并且可以很容易的验证我的回答对不对那么我就可以创建一笔交易,而这笔交易的txin就提供这个问题的答案,只要我的这个tx优先被矿工打包进入区块中,并成为最长链,那么这个问题下的钱就归我了。这个场景就是符合正向很难,逆向容易的场景。接下来就解释 比特币系统中的 CScript 到底是怎么运作的。CScript在比特币源码当中,对于CScript 单独列出了 script.c/script.h 来实现这块体系(对比把tx,block等所有实现全部放在main.c/.h来说),可见得中本聪在一开始设计这套体系的时候就把这块的内容看的相当的重要。事实上这套体系也确实很复杂,但是也是得益于这套体系,才能取得现在的地位,如果没有这个设计,比特币的实用性会被大幅度减弱。class CScript : public vector&unsigned char&\n{\n
// 把各种类型的数据序列化到 vector 中\n
CScript& operator&&(char b)
{ return (push_int64(b)); }\n
CScript& operator&&(short b)
{ return (push_int64(b)); }\n
CScript& operator&&(int b)
{ return (push_int64(b)); }\n
CScript& operator&&(long b)
{ return (push_int64(b)); }\n
CScript& operator&&(int64 b)
{ return (push_int64(b)); }\n
CScript& operator&&(unsigned char b)
{ return (push_uint64(b)); }\n
CScript& operator&&(unsigned int b)
{ return (push_uint64(b)); }\n
CScript& operator&&(unsigned short b) { return (push_uint64(b)); }\n
CScript& operator&&(unsigned long b)
{ return (push_uint64(b)); }\n
CScript& operator&&(uint64 b)
{ return (push_uint64(b)); }\n
CScript& operator&&(opcodetype opcode)\n
CScript& operator&&(const uint160& b)\n
CScript& operator&&(const uint256& b)\n
CScript& operator&&(const CBigNum& b)\n
CScript& operator&&(const vector&unsigned char&& b)\n
bool GetOp(const_iterator& pc, opcodetype& opcodeRet, vector&unsigned char&& vchRet) const\n
{\n// ....\n
void FindAndDelete(const CScript& b)\n
{\n// ...\n
}\n};\n从这个类中可以看到,其实CScript其实就是vector&char& ,没什么特别的,重要的不是它是什么,重要的是它的内容是什么,会起什么作用。可以看出其实这类的作用,是像提供了一个容器,这个容器可以存储其他类型的数据(基本类型,uint64,uint256,uint160...),换句话说,这是提供了一个容器来接受各种数据类型的序列化。但是除了基本属性之外,对于Script,定义了一个特别的东西,就是opcodetype,也就是操作符。而类中的GetOp()方法显然就是从vector&char&这样的“流”式数据中把操作符从其中识别出来的方法。所以从这里可以一窥Script的真实作用,它是由一系列操作符和数据组合而成的,由操作符持有逻辑(动作),由数据持有\"状态\"的结构体,因为它最终是被传输和存储的,所以使用vector&char&作为容器,将操作符和数据“序列化”到了这个容器中。Script的操作符对于CScript中持有的关于“操作符”相关的是opcodetype。这个操作符实际上就是一个枚举类型,如果把Script当作语言相关的概念,那么实际上opcode就是对应类似汇编中的指令。所以指令的行为是由人制定的,那么指令的表示实际上就是一个代号。下面这个枚举类型就是源码中的opcodetype,做了一些删减。enum opcodetype\n{\n
// push value
这部分的指令相当于表示这个指令后面的数据是怎么样的组织性质,\n
OP_FALSE=OP_0,\n
OP_PUSHDATA1=76,
// 0x4c 为什么是这个值其实我不太清楚,不过可以肯定的是,这个值是76那么 OP_1 就是81 也就是0x51\n
OP_PUSHDATA2,\n
OP_PUSHDATA4,\n
OP_1NEGATE,\n
OP_RESERVED,
// 81 也就是 0x51,但是为什么要求这个值是81不太清楚,但是感觉很特别\n
OP_TRUE=OP_1, // 81 \n
OP_3,\n//... 一直到Op_16\n\n
// control
// 以下是控制流指令,比如 if 这类的指令,就是作为控制流存在的了\n
OP_NOTIF,\n
OP_VERIF,\n
OP_VERNOTIF,\n
OP_ELSE,\n
OP_ENDIF,\n
OP_VERIFY,\n
OP_RETURN,\n\n
// stack ops
// 以下是对于栈的操作,这里可以理解为,栈用来保存了数据当前所处于的状态,\n
// 这些指令相当于控制栈当前的状态,可以比作在编程中对当前操作对象的把控?。下文会对整体流程进行讲解\n
OP_TOALTSTACK,\n
OP_FROMALTSTACK,\n
OP_2DROP,\n
OP_2DUP,\n
OP_3DUP,\n
OP_2OVER,\n
// ...\n\n
// splice ops
// 这些也是对数据的一些处理操作,但是这些是对栈中数据本身的内容进行操作\n
OP_SUBSTR,\n
OP_LEFT,\n
OP_RIGHT,\n
OP_SIZE,\n\n
// bit logic
// 这个和上者一样,不过是位操作\n
OP_INVERT,\n
// ...\n\n
// numeric
// 这个和上者一样,不过是数字逻辑操作\n
OP_1ADD,\n
OP_1SUB,\n
OP_2MUL,\n
OP_2DIV,\n
OP_NEGATE,\n
OP_0NOTEQUAL,\n\n
// 这个和上者一样,但是操作的是和hash加密等相关的内容,可以理解为对bitcoin系统的特有的DSL\n
OP_RIPEMD160,\n
OP_SHA1,\n
OP_SHA256,\n
OP_HASH160,\n
OP_HASH256,\n
OP_CODESEPARATOR,\n
OP_CHECKSIG,
// 这个是用的最多的,就是来判定签名是否符合的指令\n
OP_CHECKSIGVERIFY,\n
OP_CHECKMULTISIG,\n
OP_CHECKMULTISIGVERIFY,\n\n
// multi-byte opcodes\n
OP_SINGLEBYTE_END = 0xF0,\n
OP_DOUBLEBYTE_BEGIN = 0xF000,\n\n
// template matching params
// 下面这两个代表bitcoin特别的数据结构,公钥(地址)\n
OP_PUBKEY,\n
OP_PUBKEYHASH,\n\n
OP_INVALIDOPCODE = 0xFFFF,\n};\n可以看到这些指令其实都是很清晰的,只不过这些指令运行的方式有点接近汇编指令的运作方式,(c语言的栈)接下来会举例如何运行Script。Script 运行方式这里有一篇比较好的文章介绍了它的运行:这里我详细介绍一下:首先明确整体脚本的运行时基于栈运行的,而刚才上一章介绍的指令就是操作栈中元素的方式。OP_DUP:复制栈顶元素这里借用一下刚才那个链接里面的图。在源码中呢,执行Script的函数是EvalScript()而整体的运行流程就是 (script.cpp)传入脚本Script(这个脚本是把 scriptPubKey 和 scriptSig) 拼接在一起的一个总的Scriptbool VerifySignature(const CTransaction& txFrom, const CTransaction& txTo, unsigned int nIn, int nHashType)\n{\n
// 注意这里把 txin 的 scriptSig 和 txout 的 scriptPubKey 拼接在一起\n
return EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn, nHashType); \n}\n2. 创建一个 stack(栈),这个stack就是前文一直提到的栈。但是这个栈所穿了就是一个vector,就是数据结构里的那个东西bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType,\n
vector&vector&unsigned char& &* pvStackRet)\n{\n
CAutoBN_CTX pctx;\n
CScript::const_iterator pc = script.begin();\n
CScript::const_iterator pend = script.end();\n
CScript::const_iterator pbegincodehash = script.begin();\n
vector&bool& vfExec;
// 这个是暂时记录 栈中执行if判断结果的地方\n
vector&valtype& stack;
// 栈就是这个,而valtype是一个定义 typedef vector&unsigned char&\n
// ...\n}\n3. 整个的执行过程就是,首先执行了 scriptSig,那么这个scriptSig就会在栈中留下一系列的状态和数据,而这些状态和数据是为了配对scriptSig中的状态和数据(也就是为了配对问题的答案)。读取(并执行,虽然对于scriptSig应该大部分都是提供数据,不会带有执行过程)scriptSig后,那么就开始读取scriptPubKey,没读取scriptPubKey中的一个操作符,就执行一次。可以把其当作解释形语言的形式,读取一条执行一条。例如以源码中的最基础交易模板为例:bool Solver(const CScript& scriptPubKey, vector&pair&opcodetype, valtype& && vSolutionRet) // script.cpp\n{\n
// Templates\n
static vector&CScript& vTemplates;\n
if (vTemplates.empty())\n
// Standard tx, sender provides pubkey, receiver adds signature\n
vTemplates.push_back(CScript() && OP_PUBKEY && OP_CHECKSIG);\n\n
// Short account number tx, sender provides hash of pubkey, receiver provides signature and pubkey\n
vTemplates.push_back(CScript() && OP_DUP && OP_HASH160 && OP_PUBKEYHASH && OP_EQUALVERIFY && OP_CHECKSIG);\n
// ....\n}\n// 我们以bitcoin提供的 vTemplates 中的第二个为例:\n// 以下是出现相关代码的地方:\nvoid CSendDialog::OnButtonSend(wxCommandEvent& event){ //ui.cpp\n
if (fBitcoinAddress)\n
// Send to bitcoin address\n
CScript scriptPubKey;\n
scriptPubKey && OP_DUP && OP_HASH160 && hash160 && OP_EQUALVERIFY && OP_CHECKSIG;
// 这里对应的就是第二个模板 hash160是收款方地址\n
//...\n}\n\n// 生成对于这个脚本配对的 scriptSig 位于 Solver 内\n
bool Solver(const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& scriptSigRet)\n{\n
else if (item.first == OP_PUBKEYHASH) // 这里对应的是第二个模板,注意 OP_PUBKEYHASH\n
// Sign and give pubkey\n
if (hash != 0)\n
vector&unsigned char& vchSig;\n
if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig))\n
return false;\n
vchSig.push_back((unsigned char)nHashType);\n
scriptSigRet && vchSig && vchPubKey; // 除了 sig 外 还要把 pubkey 也添加进入scriptsig中 // 这里就是生成答案的地方\n
// ...\n所以对于整个执行过程就是这样的:首先对于 vector&valtype&\n来说,从 scriptSig 中压栈 vchSig 和 vchPubkey。那么栈中就拥有了 vchSig,vchPubkey。那么接下来的执行过程如下:整体的过程就是这样的。就相当于一个人提供的答案,然后验证者拿出这份答案对应的问题,然后看一眼问题,检查一下问题的结果,然后在看问题,再执行,依次执行下去的过程。所以如果问题不是公私钥配对解密,而是其他的问题,比如创建一个pubkey&& 100 && 200 && OP_1ADD && OP_EQUALVERIFY\n的问题,那么对应这个问题的答案就显然是sig && 300\n就是这样的过程。其他以上详细的介绍了整个脚本的运作流程。现在指明一些细节:数据类的序列化(&&操作符)进入脚本都会被 OP_PUSHDATA1,OP_PUSHDATA2,OP_PUSHDATA4 操作符所标明,指明这是一个数据在序列化数据的时候,注意数字 1-16 和 -1 会被认为是操作符OP_1-OP_16和OP_1NEGATE。我目前尚不清楚为何需要这样设计,或许是保留字段?class CScript : public vector&unsigned char&\n{\nprotected:\n
CScript& push_int64(int64 n)\n
if (n == -1 || (n &= 1 && n &= 16))//注意这里!\n
push_back(n + (OP_1 - 1)); // 对1-16产生了OP_1的偏移(OP_1=81)\n
CBigNum bn(n);\n
*this && bn.getvch();\n
return (*this);\n
string ToString() const\n
while (GetOp(it, opcode, vch))\n
if (!str.empty())\n
str += \" \";\n
if (opcode &= OP_PUSHDATA4)\n
str += ValueString(vch);\n
str += GetOpName(opcode); // 1-16, -1 最后会进入这个分支\n
return str;\n
}\n}\n总结Script是比特币系统中异常强大的地方,真是这种运作模式开启了之后的智能合约的风潮。其把简单的认证一个交易的归属问题的流程从简单的认证扩展到脚本的运行,粗略来看是把一个简单的东西变得复杂了,实际上是极大的扩展了“交易”的含义。使得交易可以含有“逻辑”,而不仅仅是“状态”中本聪把 “交易过程” 开创性的演化为了 “问题-答案” 的过程,重新定义了什么是“交易”另一方面正如许多人所说的,在整个指令集中没有出现循环指令,所以这个指令集不是一个图灵完备的语言,它只能按照脚本的编写顺序执行。至于为什么会这样设计?有人猜测说是中本聪认为脚本的执行不应该出现循环,否则要是有人写了死循环恶意破坏会造成很大麻烦,有人认为在这种模式下图灵完备是没有必要的,有人认为对于“交易合同”来说这些已经足够了,有人认为是中本聪没有考虑好这个问题。不管怎么说,交易的脚本绝对是使比特币成为强大功能系统中不可缺少的一环。所以后来的以太坊正是完成了中本聪最后没有完成的这个东西,成为了拥有“智能合约”能力的区块链,向去中心化理想国迈进新的一步。","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T15:51:16+08:00","url":"/p/","title":"bitcoin 源码解析 - 交易 Transaction(三) - Script","summary":"bitcoin 源码解析 - 交易 Transaction(三) - Script之前的章节已经比较粗略的解释了在Transaction体系当中的整体运作原理。接下来的章节会对这个体系进行分解,比较详细描述细节的构成。本章将要详细分析bitcoin交易中的交易脚本-script到底是什么东西。回…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":5,"likesCount":2}},"annotationDetail":null,"commentsCount":6,"likesCount":1,"FULLINFO":true}},"User":{"jin-xiao-94-7":{"isFollowed":false,"name":"金晓","headline":"","avatarUrl":"/v2-a03f59f4f9d3ad3f3a4db6fc186c4c83_s.jpg","isFollowing":false,"type":"people","slug":"jin-xiao-94-7","bio":"我超级懒的","hash":"cb3b97fbae2e613fba0f76e7bd23fc76","uid":08,"isOrg":false,"description":"","profileUrl":"/people/jin-xiao-94-7","avatar":{"id":"v2-a03f59f4f9d3ad3f3a4db6fc186c4c83","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}}},"Comment":{},"favlists":{}},"me":{},"global":{},"columns":{"next":{},"c_":{"following":false,"canManage":false,"href":"/api/columns/c_","name":"金狗喵喵喵的区块链研习","creator":{"slug":"jin-xiao-94-7"},"url":"/c_","slug":"c_","avatar":{"id":"4b70deef7","template":"/{id}_{size}.jpg"}}},"columnPosts":{},"columnSettings":{"colomnAuthor":[],"uploadAvatarDetails":"","contributeRequests":[],"contributeRequestsTotalCount":0,"inviteAuthor":""},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"drafts":{"draftsList":[],"next":{}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{}}

我要回帖

更多关于 begintransaction 的文章

 

随机推荐