Bitcoin Computer是比特币上的二层智能合约解决方案。该方案用Javascript语言编写智能合约,合约的代码和调用都放到区块链上。在链下进行合约状态的计算和校验。javascript
咱们以一个计数器合约为例来分析Bitcoin Computer的运行原理和特色。基本代码以下:java
import Computer from 'bitcoin-computer'; (async () => { const computer = new Computer.default({ seed: 'the mnemonic words', chain: 'BSV', network: 'testnet' }); class Counter { constructor(n) { this.n = n } inc() { this.n += 1 } } const counter = await computer.new(Counter, [2]); await counter.inc(); console.log(counter); })()
Computer
实例。Computer
实例。Computer
是Bitcoin Computer的系统库,能够经过npm等包管理工具进行安装。Counter
合约。n
的值。inc
方法每被调用一次,成员变量n
就会加1。实现了一个简单的计数功能。computer
的new
方法,将Counter
合约部署到区块链上。第二个参数是Counter
类的构造函数的参数列表。也就是说咱们在链上部署了一个Counter
合约,合约的变量n
的初始值为2。同时返回了一个链上合约变量counter
。await
说明部署合约是须要与外部交互的。很显然这一步须要经过网络将代码写入区块链。inc
方法运行合约,让计数器加1。这里也用了关键字await
,说明合约的执行也是须要与区块链交互的。经过console.log
语句打印出来的运行结果以下:node
Counter { n: 3, _id: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0', _rev: '1aab4b23834b0502c15db98433d7eb50e5440f2a64b4a2553a81b655ae6e2696:0', _rootId: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0' }
看到这个结果,咱们首先能够猜想出n
表示合约中成员变量的值,初始值为2,作了一次inc
操做后值加1,也就是3。与猜想一致。web
观察_id
字段,看值的格式,咱们猜这也许是个txid,那咱们打开区块浏览器查查看,发现该tx的第0个output脚本部分用ASCII解码后内容以下:npm
Q!6{ YÌk¥Í¹;;ÜadžUF"Q³`ƒƒÅ¡´Ï_ÌQ®L®{"__cls":"class Counter { \n constructor(n) { \n this.n = n\n }\n inc() { \n this.n += 1\n }\n }","__index":{"obj":0},"__args":[2],"__func":"constructor"}u
其中把可读字符串部分单独提出来并格式化后内容以下:api
{ "__cls":"class Counter {\n constructor(n) {\n this.n = n\n }\n inc() {\n this.n += 1\n }\n }", "__index":{ "obj":0}, "__args":[2], "__func":"constructor" }
能够推测,这个JSON数据应该就是合约部署数据,合约的javascript代码、初始化参数等都放到了链上。根据这个内容,就能够恢复出一个javascript语言的Counter
类实例。浏览器
ASM格式的脚本以下:网络
1 03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f 1 OP_CHECKMULTISIG 7b225f5f636c73223a22636c61737320436f756e746572207b5c6e20202020636f6e7374727563746f72286e29207b5c6e202020202020746869732e6e203d206e5c6e202020207d5c6e20202020696e632829207b5c6e202020202020746869732e6e202b3d20315c6e202020207d5c6e20207d222c225f5f696e646578223a7b226f626a223a307d2c225f5f61726773223a5b325d2c225f5f66756e63223a22636f6e7374727563746f72227d OP_DROP
脚本能够分两个部分:async
OP_CHECKMULTISIG
为止,是一个多重签名模板,这个多签模板中只有一个公钥,是个1/1签名。说明这个UTXO只能被该公钥对应的私钥花费。可见,合约的运行是有权限控制的。同时,咱们还能够发现0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0
表示的是一个outpoint,:
前面为txid,后面为output index。svg
观察_rev
字段,这也是个outpoint,经过区块链查询发现ASCII格式内容以下:
Q!6{ YÌk¥Í¹;;ÜadžUF"Q³`ƒƒÅ¡´Ï_ÌQ®0{"__index":{"obj":0},"__args":[],"__func":"inc"}u
剥去不可读字符并格式化后内容以下:
{ "__index":{ "obj":0}, "__args":[], "__func":"inc" }
这部分记录了合约运行时被调用的方法和参数。
ASM格式的脚本以下:
1 03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f 1 OP_CHECKMULTISIG 7b225f5f696e646578223a7b226f626a223a307d2c225f5f61726773223a5b5d2c225f5f66756e63223a22696e63227d OP_DROP
不出所料,套路跟部署部分是同样的。
同时,咱们还发现_rev
tx的input之一就是_id
,也就是部署合约的outpoint。很明显,合约从部署到运行,新的状态花费前一个状态的output而造成的新output,造成了一条tx链。_id
记录合约的最初outpoint,也就是部署outpoint,_rev
记录最新状态的outpoint。
咱们运行一个新的程序,看合约是如何在不一样电脑之间实现同步的。
import Computer from 'bitcoin-computer'; (async () => { const computer = new Computer.default({ seed: 'the same mnemonic words', chain: 'BSV', network: 'testnet' }); const counter = await computer.sync('1aab4b23834b0502c15db98433d7eb50e5440f2a64b4a2553a81b655ae6e2696:0'); console.log(counter); await counter.inc(); console.log(counter); })()
computer.sync
函数经过网络从区块链获取部署和运行数据,参数就是合约最新的outpoint。sync
运行完毕后得到的counter变量为:
Counter { n: 3, _rev: '1aab4b23834b0502c15db98433d7eb50e5440f2a64b4a2553a81b655ae6e2696:0', _id: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0', _rootId: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0' }
这与前面运行的结果是同样的。咱们能够推测整个sync
过程大体是这样的:
接下来再运行一次合约,结果以下:
Counter { n: 4, _rev: '9407b32d7e5e701949a4b00accbd74f04c4fb651451904d77ec1a8ce56d334b4:0', _id: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0', _rootId: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0' }
结果如咱们所预期。
此时我突发奇想,假设我不一样步到最新的合约状态,而是同步到中间的状态,而后就运行,会怎么样呢?
咱们复制一份一样的代码,由于最新的n
值已经变成了4,而代码中sync
的参数是n
值为3时的outpoint,因此咱们同步的是一个中间状态。
运行代码,同步后的counter为
Counter { n: 3, _rev: '1aab4b23834b0502c15db98433d7eb50e5440f2a64b4a2553a81b655ae6e2696:0', _id: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0', _rootId: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0' }
这一步跟咱们的预期同样,同步下来了合约的中间状态。
而后来看看await counter.inc()
的执行结果:
(node:85000) UnhandledPromiseRejectionWarning: Error: Communication Error message Request failed with status code 400 request post https://api.whatsonchain.com/v1/bsv/test/tx/raw transaction { ...... } response "Missing inputs" ......
运行失败了。我用......
忽略了一些细节。经过关键信息Missing inputs
咱们能够知道,失败缘由是要花费的UTXO不存在。这就符合逻辑了,迁移状态就须要花费该状态对应的UTXO,但这是个中间状态,output已经被花费过了,tx遭到矿工拒绝。
Bitcoin Computer用UTXO为模型,解决了合约执行的前后顺序问题。
在_合约同步_这一节能够观察到这样一个细节:合约部署和合约同步两部分代码,在建立computer
实例时,用了相同的助记词。若是咱们用不一样的助记词,在进行合约的同步和运行时会怎么样呢?接下来咱们就试试。
把合约同步部分的代码复制一份,改掉助记词部分,sync
参数改成合约最新的outpoint 9407b32d7e5e701949a4b00accbd74f04c4fb651451904d77ec1a8ce56d334b4:0
,而后运行。
首先,合约的同步是正确的。同步下来的counter变量内容为:
Counter { n: 4, _rev: '9407b32d7e5e701949a4b00accbd74f04c4fb651451904d77ec1a8ce56d334b4:0', _id: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0', _rootId: '0a21910877dbfa8990eec667253f140089ea7834f3677f4cab4df3ae416cb379:0' }
接下来合约执行await counter.inc()
语句失败,失败信息为
(node:26304) UnhandledPromiseRejectionWarning: Error: Communication Error message Request failed with status code 400 request post https://api.whatsonchain.com/v1/bsv/test/tx/raw transaction { ...... } response "16: mandatory-script-verify-flag-failed (Operation not valid with the current stack size)" ......
在_合约运行_小节里,咱们知道合约的脚本是以多签名为基础的,不一样的助记词没法计算出相同的公私钥对,所以没法解锁记录最新状态的UTXO,因此会产生上述失败。
是否可让多个私钥运行同一个合约呢?能够的。Bitcoin Computer的合约里有一个关键成员变量_owers
用于管理合约的权限。咱们来看一个新的例子:
import Computer from 'bitcoin-computer'; (async () => { const computerA = new Computer.default({ seed: 'the mnemonic words', chain: 'BSV', network: 'testnet' }); const computerB = new Computer.default({ seed: 'different mnemonic words', chain: 'BSV', network: 'testnet' }); class Counter { constructor(n, pubKeys) { this.n = n; this._owners = pubKeys; } inc() { this.n += 1; } } const pubKeys = [computerA.db.wallet.getPublicKey().toString(), computerB.db.wallet.getPublicKey().toString()]; const counter = await computerA.new(Counter, [0, pubKeys]); await counter.inc(); console.log(counter); const syncCounter = await computerB.sync(counter._rev); await syncCounter.inc(); console.log(syncCounter); })();
computerA
和computerB
,每一个实例都有与对方不一样的公私钥对。pubKeys
,该参数用来表示一组公钥,同时该参数传给了Bitcoin Computer系统预留的成员变量_owners
。computerA
建立合约实例counter
,并把computerA
和computerB
的两个公钥都传给了合约。其中.db.wallet
是Computer中的组件,能够用来获取助记词对应的公钥等信息。computerA
的counter
运行一次inc
方法。观察结果。computerB
把计数器合约同步到syncCounter
中,用syncCounter
运行一次inc
方法。观察结果。先看computerA
运行inc
方法后的结果:
Counter { n: 1, _owners: [ '03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f', '02c9788a60264523ba77500e19a0b2626c9b09b25daa16cfee09b4e1135d610c90' ], _id: 'dfd0a1a6792ff3fddc9277d8d18577ce0285a0c549ee77ec3adb0c4e4decc531:0', _rev: '2b302ed6eb74d92b51ce1491e9cc108a0f594aaa451efde9822b4968bb6fc3a3:0', _rootId: 'dfd0a1a6792ff3fddc9277d8d18577ce0285a0c549ee77ec3adb0c4e4decc531:0' }
首先说明合约执行成功,咱们再经过区块链来查看ASM格式的合约部署脚本
1 03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f 02c9788a60264523ba77500e19a0b2626c9b09b25daa16cfee09b4e1135d610c90 2 OP_CHECKMULTISIG 7b225f5f636c73223a22636c61737320436f756e746572207b5c6e20202020636f6e7374727563746f72286e2c207075624b65797329207b5c6e202020202020746869732e6e203d206e3b5c6e202020202020746869732e5f6f776e657273203d207075624b6579733b5c6e202020207d5c6e20202020696e632829207b5c6e202020202020746869732e6e202b3d20313b5c6e202020207d5c6e20207d222c225f5f696e646578223a7b226f626a223a307d2c225f5f61726773223a5b302c5b22303333363762353963633662613563646239336233626463363163373031383635353436323235316233363038333833633561316234616463663566316263633166222c22303263393738386136303236343532336261373735303065313961306232363236633962303962323564616131366366656530396234653131333564363130633930225d5d2c225f5f66756e63223a22636f6e7374727563746f72227d OP_DROP
发现多签名中有两个公钥了,而不是以前咱们看到的一个,这两个公钥就对应了合约中_owners
中的两个变量。说明Bitcoin Computer在部署时对_owners
变量作了特殊处理,让该变量里的全部公钥都放入了多签中。若是不设置该参数,则只会放入建立者computer实例的公钥。
既然computerB
的公钥也在多签里,那么咱们就能够预测computerB
的合约执行也会成功。
computerB
的syncCounter
执行inc
方法结果以下
Counter { n: 2, _owners: [ '03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f', '02c9788a60264523ba77500e19a0b2626c9b09b25daa16cfee09b4e1135d610c90' ], _rev: 'c6cfdc8dcbaa3b331641f21a26173227664d685b31ec366f4f246bbf28be07ba:0', _id: 'dfd0a1a6792ff3fddc9277d8d18577ce0285a0c549ee77ec3adb0c4e4decc531:0', _rootId: 'dfd0a1a6792ff3fddc9277d8d18577ce0285a0c549ee77ec3adb0c4e4decc531:0' }
跟咱们预想的同样,用computerB
也能够执行成功。
可见,Bitcoin Computer是经过多签名的方式来让多个拥有不一样私钥的用户执行同一个合约。
Bitcoin Computer巧妙地将javascript、区块链、UTXO、多签名等融合在一块儿,创造了一个开发友好的二层合约解决方案。若是想进一步了解,能够参考官方文档。 之后将会继续对Bitcoin Computer作进一步探讨。