对于BitcoinComputer——Token合约感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍bitcoinwin合约交易,并为您提供关于"message":"Unexpectedtoke
对于Bitcoin Computer——Token 合约感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍bitcoinwin合约交易,并为您提供关于"message": "Unexpected token < in JSON at position 0", "stack": "SyntaxError: Unexpected token < in JSON at position 0"、(Axios 拦截器) 从后台拿到 token 后存到 headers 中方法以及 token 过期报 401 的解决方法、Bitcoin Cash币值得长期持有吗?Bitcoin Cash币值得投资吗?、Bitcoin Cash币是什么币?Bitcoin Cash币有价值吗?的有用信息。
本文目录一览:- Bitcoin Computer——Token 合约(bitcoinwin合约交易)
- "message": "Unexpected token < in JSON at position 0", "stack": "SyntaxError: Unexpected token < in JSON at position 0"
- (Axios 拦截器) 从后台拿到 token 后存到 headers 中方法以及 token 过期报 401 的解决方法
- Bitcoin Cash币值得长期持有吗?Bitcoin Cash币值得投资吗?
- Bitcoin Cash币是什么币?Bitcoin Cash币有价值吗?
Bitcoin Computer——Token 合约(bitcoinwin合约交易)
前面介绍了 Bitcoin Computer 的原理以及如何发送比特币。这次介绍 Token 方案。要理解本文,需要先阅读前两篇文章。
创建合约
先来看示例代码
import Computer from ''bitcoin-computer'';
class Token{
constructor(supply, to) {
this.tokens = supply;
this._owners = [to];
}
send(amount, to){
if(this.tokens < amount){
throw new Error(`Not enough tokens: ${
amount} < ${
this.tokens}`);
}
this.tokens -= amount;
return new Token(amount, to);
}
}
(async () => {
const computerA = new Computer.default({
seed: ''seed of computer A'',
chain: ''BSV'',
network: ''testnet''
});
let tokens = await computerA.new(Token, [100, computerA.db.wallet.getPublicKey().toString()]);
console.log(tokens);
})();
- 首先定一个了一个名为
Token
的智能合约。- 成员变量
tokens
表示 token 数量;成员变量_owners
表示这些数量的 token 属于谁。 - 构造函数有两个参数,
supply
表示该合约实例有多少个 token,to
是 token 拥有者的公钥。如果是首次创建合约,那么supply
表示 token 总量是多少。 send
方法用于发送 token,amount
参数表示发送多少,to
参数是接收方的公钥。
- 成员变量
- 创建
Computer
实例computerA
。 computerA
创建Token
合约,总量 100,发放到自己的公钥里。
运行结果:
Token {
tokens: 100,
_owners: [
''03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f''
],
_id: ''37e47c71219a0f7f3b80c04b24bbfa4613364cb1a81a9b166a33178c375ba628:0'',
_rev: ''37e47c71219a0f7f3b80c04b24bbfa4613364cb1a81a9b166a33178c375ba628:0'',
_rootId: ''37e47c71219a0f7f3b80c04b24bbfa4613364cb1a81a9b166a33178c375ba628:0''
}
可以在区块浏览器查看这笔转账,如果你读了前两篇文章,可以猜测出合约输出的内容,为了方便查看,我使用 ASM 格式的脚本,但把其中的可见字符串部分直接用 UTF8 格式表示:
1
03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f
1
OP_CHECKMULTISIG
{"__cls":"class Token{\n constructor(supply, to) {\n this.tokens = supply;\n this._owners = [to];\n }\n\n send(amount, to){\n if(this.tokens < amount){\n throw new Error(`Not enough tokens: ${amount} < ${this.tokens}`);\n }\n\n this.tokens -= amount;\n return new Token(amount, to);\n }\n}","__index":{"obj":0},"__args":[100,"03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f"],"__func":"constructor"}
OP_DROP
多签名的公钥为 ComputerA
的公钥。合约数据部分为 Token
类的定义,以及构造函数名称及运行参数,表示用构造函数和参数创建了一个合约实例。
token 转账
const computerB = new Computer.default({
seed: ''seed of computer B'',
chain: ''BSV'',
network: ''testnet''
});
await tokens.send(10, computerB.db.wallet.getPublicKey().toString());
console.log(tokens);
- 创建另一个
Computer
实例computerB
。 computerA
把 10 个 token 转给computerB
。
运行结果:
Token {
tokens: 90,
_owners: [
''03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f''
],
_id: ''37e47c71219a0f7f3b80c04b24bbfa4613364cb1a81a9b166a33178c375ba628:0'',
_rev: ''171c0c8713b8c8a97c556b286c95b821e0ff64170b681c946d4123aee9134e79:0'',
_rootId: ''37e47c71219a0f7f3b80c04b24bbfa4613364cb1a81a9b166a33178c375ba628:0''
}
可以看到,转账结束后,computerA
的 token 数量还剩下 90。
通过区块链浏览器查看这个 tx,输出结构上跟以往的例子有些不一样。以往的合约都有两个输出,一个合约输出,一个找零输出。而这个 token 转账 tx 有三个输出,其中第三个是找零。接下来详细看看前两个输出。
输出 0
1
03367b59cc6ba5cdb93b3bdc61c7018655462251b3608383c5a1b4adcf5f1bcc1f
1
OP_CHECKMULTISIG
{"__index":{"obj":0,"res":1},"__args":[10,"02c9788a60264523ba77500e19a0b2626c9b09b25daa16cfee09b4e1135d610c90"],"__func":"send"}
OP_DROP
很明显这是一个合约运行输出脚本,多签名的公钥是 computerA
的,数据部分是合约运行的函数和参数。
跟以往例子不同的是,合约数据中的__index
字段里多了一个 res
字段,值为 1。因为开发者并没有公布协议,所以并不能准确知道这个字段的含义,我猜测这里的 1 表示输出 1:这个合约的运行给输出 1 提供了一些必要信息。
输出 1
1
02c9788a60264523ba77500e19a0b2626c9b09b25daa16cfee09b4e1135d610c90
1
OP_CHECKMULTISIG
{"__cls":"class Token{\n constructor(supply, to) {\n this.tokens = supply;\n this._owners = [to];\n }\n\n send(amount, to){\n if(this.tokens < amount){\n throw new Error(`Not enough tokens: ${amount} < ${this.tokens}`);\n }\n\n this.tokens -= amount;\n return new Token(amount, to);\n }\n}"}
OP_DROP
这个输出的多签名公约是 computerB
的,所以应该表示一部分 token 转到了 computerB
名下。合约数据部分是一份 Token 类的定义。
到这里,需要再看看 Token
类中 send
方法的实现。其中真正的转账代码是通过再创建一个 Token
类实例来实现的,构造函数的参数就是发送数量和接收者公钥。所以,我猜测,合约代码中这行代码的逻辑体现在 tx 中就是这个输出。
同时,合约数据中并没有出现合约创建 tx 输出中的__args
和__func
字段,__args
字段应该是来自输出 0 中的__args
,这也就是我们前面说到的输出 0 给输出 1 提供了必要信息。
computerB
的视角
前面我们用 computerA
的 token 实例转完账后,token 数量还剩 90。接下来我们用 compuerB
把转账 tx 同步下来,用它的视角来看看 token 的数量。
因为输出 1 才是属于 computerB
的,所以首先先把版本号的 outputIndex 部分改为 1,然后再进行同步。
const computerBTokenRev = tokens._rev.split('':'')[0]+'':1'';
tokens = await computerB.sync(computerBTokenRev);
console.log(tokens);
运行后 tokens 变量的值为:
Token {
tokens: 10,
_owners: [
''02c9788a60264523ba77500e19a0b2626c9b09b25daa16cfee09b4e1135d610c90''
],
_rev: ''171c0c8713b8c8a97c556b286c95b821e0ff64170b681c946d4123aee9134e79:1'',
_id: ''171c0c8713b8c8a97c556b286c95b821e0ff64170b681c946d4123aee9134e79:1'',
_rootId: ''37e47c71219a0f7f3b80c04b24bbfa4613364cb1a81a9b166a33178c375ba628:0''
}
属于 computerB
的 token 数量为 10,跟预期一致。
同时还发现一个有意思的现象:_id
字段不再是合约部署的 outPoint,而是转账 tx 的输出 1。而_rootId
字段仍然是合约部署的 outPoint。因此,我猜测_rootId
表示部署合约的 outPoint,_id
表示当前合约实例开始创建的 outPoint(属于 compuerB
的合约实例在部署时还没有,在 compuerA
转账后才出现)。
安全性
上链前保护
Token 方案的首要安全性是对 token 数量的计算是否正确。我们沿用上面的程序继续对安全性进行测试。
tokens = await computerA.sync(''171c0c8713b8c8a97c556b286c95b821e0ff64170b681c946d4123aee9134e79:0'');
tokens.send(91, computerB.db.wallet.getPrivateKey().toString());
computerA
把它的合约 UTXO 同步下来,此时还剩 90 个 token。- 尝试给
computerB
转出 91 个 token。
运行时抛出了一个异常:
UnhandledPromiseRejectionWarning: Error: Not enough tokens: 91 < 90
仔细看 send
函数的实现,最开始就是对 token 数量的保护,如果实例没有那么多 token,就抛出了异常,并跳出了 send
函数的运行。也就是说会发送失败,从而在上链前就保证了 token 数量的正确。
所以,Bitcoin Computer 用 Javascript 的异常功能巧妙地对合约的安全性进行了保护。
上链后保护
如果手工构造一笔不合法的转账,是否可以突破 token 数量的异常保护呢?我构造了一笔转账,标记 computerA
给 computerB
转了 91 个 token,实际上 computerA
只有 90 个。
{
"__index":{"obj":0,"res":1},
"__args":[91,"02c9788a60264523ba77500e19a0b2626c9b09b25daa16cfee09b4e1135d610c90"],
"__func":"send"
}
对于已经在链上的非法转账,我们看 Bitcoin Computer 会如何表现:
const tokens = await computerA.sync(''51bdee16d6b452aec909908c228c355ab7ca7bc311952ce7897d5e9bfa2fcb3d:0'');
console.log(tokens);
computerA
从记载非法转账记录的 outPoint 加载合约数据,运行程序,抛出了如下异常:
UnhandledPromiseRejectionWarning: Error: Not enough tokens: 91 < 90
再来看看 computerB
加载非法转账会如何表现:
const tokens = await computerB.sync(''51bdee16d6b452aec909908c228c355ab7ca7bc311952ce7897d5e9bfa2fcb3d:1'');
console.log(tokens);
代码运行时仍然抛出了异常:
UnhandledPromiseRejectionWarning: Error: Not enough tokens: 91 < 90
综上,虽然不合法的合约数据可以手动写入链上,但在运行 Javascript 合约代码时,合约可以检查出逻辑错误,并抛出异常,程序停止运行。
总结
Bitcoin Computer 的 Token 合约虽然简短,但实现上比较精巧:
- 通过创建新的合约实例实现转账。
- 直接用 Javascript 代码对 token 数量进行保护。
后记
写完了三篇关于 Bitcoin Computer 的介绍文章,这个系列就暂告一断落了。
由于 Bitcoin Computer 的协议并没有公开,我只是凭经验和测试来猜测 Bitcoin Computer 的运行原理,细节不一定准确,欢迎大家一起交流。同时也希望开发者可以尽快公布协议,对于二层合约方案来说,协议的公开性是必须的,否则无法满足用户的安全需求。
另外,有一个原理类似的二层合约方案 Run,该方案只运行在 BSV 链,RelayX 基于该方案发布了 USDC Token。最初我是想介绍这个方案的,但由于该方案没有公开代码和文档,所以选择了 Bitcoin Computer。希望 Run 也可以尽快公布自己的协议、文档和代码。
"message": "Unexpected token < in JSON at position 0", "stack": "SyntaxError: Unexpected token < in JSON at position 0"
如何解决"message": "Unexpected token < in JSON at position 0", "stack": "SyntaxError: Unexpected token < in JSON at position 0"
const express = require(''express'');
const {graphqlHTTP} = require(''express-graphql'');
const schema = require(''./schema/schema'');
const app = express();
app.get(''/graphql'',graphqlHTTP({
graphiql:true,schema: schema
}))
app.listen(4000,()=>{
console.log("listining to port 4000..")
})
const graphql = require(''graphql'');
const{
GraphQLObjectType,GraphQLID,GraphQLString,GraphQLInt,GraphQLSchema
} = graphql
const UserType = new GraphQLObjectType({
name: ''user'',description: ''Documentation for User...'',fields: ()=>({
id: {type: GraphQLString},name: {type: GraphQLString},age: {type: GraphQLInt}
})
});
const RootQuery = new GraphQLObjectType({
name: ''RootQueryType'',description: ''description'',fields: {
user: {
type: UserType,args: {id: {type: GraphQLString}},resolve(parent,args){
let user = {
id: ''345'',age: 34,name: ''Aman''
}
return user;
}
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery
})