前端请求智能合约的思路
从工作分工上来看,前端工程师完全可以把智能合约看做是一个后端工程师给你的接口,毕竟solidty的主要工作也是处理和返回数据的,和普通的后端工作内容相差不大,只不过代码是部署在区块链上的。
因为代码是部署在区块链上的,所以在发送请求时会与传统的请求有所不同,但本质是一样的,接下来我们要搞清楚以下三点,就能明白它的本质为什么是一样的:
1.什么是abi,我们为什么要用到它
2.为什么要借助第三方包,而不是直接请求服务器,它都有哪些作用
3.请求智能合约的原理是什么,区块是如何执行的
一.什么是abi,我们为什么要用到它
先看代码,因为我对ether比较熟悉,这里用ether做演示:
//区块链本质是由多个服务器跑相同的程序,存储相同的数据,然后不停地相互同步来形成的信任网络 //我们只需要链接其中一个,然后请求这个服务器即可,至于数据同步的事情不用我们处理 //provider我们称之为节点提供者(每个组成区块的服务器我们称之为节点),存储节点信息和地址信息 //我这里模拟的是DAPP的流程,节点和用户的相关信息存在window.ethereum中,可以直接获取 let provider = new ethers.providers.Web3Provider(window.ethereum) //signer(签名者),该信息存在节点提供者中并自动与节点关联 let signer = provider.getSigner(); //这里将合约地址,abi,还有签名者之间做关联,生成一个合约对象,之后就可以调用合约对象的方法了 let contract= new ethers.Contract('合约地址', erc20abi, signer); //这里调用了合约代币的转账方法 // ethers.utils.parseEther()是ether.js工具包中的一个方法,可以将数字字符串乘上10**16,没有精度损耗 let data = await contract.transfer("收币地址", ethers.utils.parseEther("1")) .catch(function(err) { console.log(err) }) //data是本次调用生成的链上数据,不是代码执行完后的返回值 console.log(data) //监听data中的交易hash,来获取交易结果 let data2=await provider.waitForTransaction(data.hash); console.log(data2)
data返回值:
{ "hash": "0xe632c016cea540b54ccde8343378e615174040fbaae68fce44ab1ea44a89a531", "type": 0, "accessList": null, "blockHash": null, "blockNumber": null, "transactionIndex": null, "confirmations": 0, "from": "0x173f8Ce8356dD214813f4874C316739F560D8022", "gasPrice": { "type": "BigNumber", "hex": "0x1ff973cafa8000" }, "gasLimit": { "type": "BigNumber", "hex": "0x760e" }, "to": "0x2c4eb3c76D7115E210Fadf3cBFe8E0a3d5b8448F", "value": { "type": "BigNumber", "hex": "0x00" }, "nonce": 207, "data": "0xa9059cbb000000000000000000000000173f8ce8356dd214813f4874c316739f560d80220000000000000000000000000000000000000000000000000de0b6b3a7640000", "r": "0xd8c09aab22974fc057010152929fcb4b134a431c707c2f212f619f18297eb485", "s": "0x69f6e8d9f3581625797bc20cfdd2951ef225e5f24366d8f4128e8e6fdfd70316", "v": 2093, "creates": null, "chainId": 1029 }
data2返回值:
{ "to": "0x2c4eb3c76D7115E210Fadf3cBFe8E0a3d5b8448F", "from": "0x173f8Ce8356dD214813f4874C316739F560D8022", "contractAddress": null, "transactionIndex": 0, "gasUsed": { "type": "BigNumber", "hex": "0x6a09" }, "logsBloom": "0x04000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000008000000800000000000000000000100000000000000000000000020000000000000000000000010000000000080000010000000000000000000040000000000000000000000004000000000000000000000000000200000000000000000000000000400000000000000000000000000000000004000000002000000000001000000000000000000000000000000100000000000400000000000000000000000000000000000000000000000000100000000100000", "blockHash": "0x7c85a7911b5e9c8fb3bf40898167873e4beccd535407fdae3aea777246014936", "transactionHash": "0xe632c016cea540b54ccde8343378e615174040fbaae68fce44ab1ea44a89a531", "logs": [ { "transactionIndex": 0, "blockNumber": 20938930, "transactionHash": "0xe632c016cea540b54ccde8343378e615174040fbaae68fce44ab1ea44a89a531", "address": "0x2c4eb3c76D7115E210Fadf3cBFe8E0a3d5b8448F", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000173f8ce8356dd214813f4874c316739f560d8022", "0x000000000000000000000000173f8ce8356dd214813f4874c316739f560d8022" ], "data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", "logIndex": 0, "blockHash": "0x7c85a7911b5e9c8fb3bf40898167873e4beccd535407fdae3aea777246014936" }, { "transactionIndex": 0, "blockNumber": 20938930, "transactionHash": "0xe632c016cea540b54ccde8343378e615174040fbaae68fce44ab1ea44a89a531", "address": "0x0000000000000000000000000000000000001010", "topics": [ "0x4dfe1bbbcf077ddc3e01291eea2d5c70c2b422b415d95645b9adcfd678cb1d63", "0x0000000000000000000000000000000000000000000000000000000000001010", "0x000000000000000000000000173f8ce8356dd214813f4874c316739f560d8022", "0x000000000000000000000000f4c2f1d772488cc6d3ec3c703b9710fa2c0e227e" ], "data": "0x00000000000000000000000000000000000000000000000d3e69b71ddbce800000000000000000000000000000000000000000000010708542e952a17a361000000000000000000000000000000000000000000000ff9c1d5a421812e455d3b8000000000000000000000000000000000000000000107078047f9b839e679000000000000000000000000000000000000000000000ff9c2a98abcf30c02453b8", "logIndex": 1, "blockHash": "0x7c85a7911b5e9c8fb3bf40898167873e4beccd535407fdae3aea777246014936" } ], "blockNumber": 20938930, "confirmations": 2, "cumulativeGasUsed": { "type": "BigNumber", "hex": "0x6a09" }, "effectiveGasPrice": { "type": "BigNumber", "hex": "0x1ff973cafa8000" }, "status": 1, "type": 0, "byzantium": true }
大家可以看到abi只在构建合约的时候用到过一次,其他地方并没有使用到
let contract= new ethers.Contract('合约地址', erc20abi, signer);
下面是abi的样子
[ { "inputs": [ { "internalType": "string", "name": "_name", "type": "string" }, { "internalType": "string", "name": "_symbol", "type": "string" }, { "internalType": "uint256", "name": "_decimals", "type": "uint256" }, { "internalType": "uint256", "name": "_totalSupply", "type": "uint256" } ], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "_owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "_spender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "_value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "_from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "_to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "_value", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "constant": true, "inputs": [ { "internalType": "address", "name": "_owner", "type": "address" }, { "internalType": "address", "name": "_spender", "type": "address" } ], "name": "allowance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "internalType": "address", "name": "_spender", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [ { "internalType": "address", "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "name", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "internalType": "address", "name": "_to", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [ { "internalType": "address", "name": "_from", "type": "address" }, { "internalType": "address", "name": "_to", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" } ]
abi就是一个json文件,在合约发布后生成,由solidity工程师导出,前端使用它的主要作用就是构建合约。里面存了这个合约里的所有方法,事件以及请求参数的限制,返回值的类型等等,可以看作是一个接口文档,只不过没有注释。
我们在调用合约的时候会先经过abi的检测,不存在的方法,错误的参数都会直接在这里被拦截,经过abi验证的请求才有可能正常发送给区块。
这里为什么说有可能那,是因为abi只能做到数据类型和方法的判断,至于说方法能不能正常执行下去还是要在合约上过一遍的。
二.为什么要借助第三方包,而不是直接请求服务器,它都有哪些作用
我们借助的第三方包有web3和ether两种,其中ether是web3的二次封装,个人推荐使用ether,能大幅度提升开发效率。他们的功能有以下三种:
1.构建交易中的对象:从上面的例子中来看,我们通过ether创建了节点,签名者,合约三个对象,然后才能开始和区块上的合约进行交互,如果不引用第三方包我们其实也能构建这三个对象,但效率就不言而喻了。
2.成熟的工具包:区块链上的数据和我们平时给用户看到的实际上相差很大,需要通过一些工具包进行转换,就比如说上面代码中的
ethers.utils.parseEther("1")
他将1变成了1*10**18,如果我们自己计算会有精度损耗不说,当量大时会自动变成科学计数法,给我们带来很多麻烦。
3.交易签名并发送:这点也是最重要的一点,不借助第三方工具包,签名这部分的工作量极大,靠我们个人是完成不了的,而如果使用了ether,我们只需要在构建合约对象时传进去即可,剩下的包ether会帮我们签名并发送,能省下很多步骤。
总的来说使用了ether之后与合约进行交互,写代码的速度甚至要比正常的请求后端要快。
三.请求智能合约的原理是什么,区块链是如何执行的
请求智能合约的原理就是网页发起正常的http请求,只不过这个方法经过第三方包的封装让我们在写代码时感受不到了,但如果打开开发者工具你可以看到如下图所示
下面我们讲一下区块链的执行原理,我们看一下下面这块代码
let data = await contract.transfer("收币地址", ethers.utils.parseEther("1")) .catch(function(err) { console.log(err) }) //data是本次调用生成的链上数据,不是代码执行完后的返回值 console.log(data) //监听data中的交易hash,来获取交易结果 let data2=await provider.waitForTransaction(data.hash); console.log(data2)
其中的data只有交易hash和交易数据,连块hash都没有,更不用说请求结果了,这是为什么?这里我们需要理解一下区块链的运行原理,主要就是矿工费机制的影响。
矿工机制是为了鼓励用户部署节点,完善生态。地址在发起交易的时候需要为这次交易提供矿工费,矿工费由广播这次交易节点获得。
所以用户发起交易后这笔交易只会生成一个待执行的签名信息,其中包括一个唯一的交易hash和要广播的交易数据以及矿工费,然后在队列中等待打包。矿工每次可以打包一个块,一个块收取的矿工费总数是固定的,会优先打包给的矿工费高的交易,打包完后整个块会一起执行,所以如果你发现你的一笔交易迟迟没有反馈结果,就要考虑提高矿工费了。交易被打包成块执行完以后才会给反馈结果。
综上所述,区块链的交易发起和反馈结果在设计上是两部分,需要对应处理。data就是发起部分的数据,data2就是监听这次交易hash获取到的数据。
我一直在使用ether,web3用来写了dom感觉过于繁琐,在实际项目中几乎没有用到,但有些大佬说web3才是厉害的,在此希望懂的朋友给点建议,应该深耕哪个框架,并说下原因