前端请求智能合约的思路

03-18 1258阅读

从工作分工上来看,前端工程师完全可以把智能合约看做是一个后端工程师给你的接口,毕竟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才是厉害的,在此希望懂的朋友给点建议,应该深耕哪个框架,并说下原因

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]