Web 应用程序无法与通过 Truffle 部署的以太坊智能合约正确通信
Posted
技术标签:
【中文标题】Web 应用程序无法与通过 Truffle 部署的以太坊智能合约正确通信【英文标题】:Web Application doesn't communicate correctly with Ethereum smart contract deployed through Truffle 【发布时间】:2021-09-03 18:52:37 【问题描述】:我在 Solidity 中编写了这个非常简单的智能合约,它允许用户将待办事项添加到他们的个人列表中,获取他们的待办事项列表等等。
pragma solidity ^0.8.0;
contract ToDo
struct Task
string content;
bool completed;
mapping(address => Task[]) private tasks;
function addTask(string memory content) public
tasks[msg.sender].push(Task(content, false));
function changeTaskState(uint256 taskId) public
tasks[msg.sender][taskId].completed = !tasks[msg.sender][taskId].completed;
function editTaskContent(uint256 taskId, string memory content) public
tasks[msg.sender][taskId].content = content;
function getTasks() public view returns(Task[] memory)
return tasks[msg.sender];
当通过 Truffle 部署并在 Truffle(develop)
终端中测试时,这完全符合预期:
truffle(develop)> const todo = await ToDo.deployed()
undefined
truffle(develop)> todo.getTasks()
[]
truffle(develop)> todo.addTask("Hello, world!")
tx: '0x7e607352c1ab8f6532c5b43e282eb20f29d5bfa451dfbb873bac3506df00cb1a',
receipt:
transactionHash: '0x7e607352c1ab8f6532c5b43e282eb20f29d5bfa451dfbb873bac3506df00cb1a',
transactionIndex: 0,
blockHash: '0x98b361190eadf1905c3e15b5054aa4ace8eaa33a2b4d35898f78e2165ea996a2',
blockNumber: 5,
from: '0x3455100c0b0617afbf0f53db5e5c07366e20791b',
to: '0x645a78fe8eb3529291ba63a8e420d26c7baf61a0',
gasUsed: 66634,
cumulativeGasUsed: 66634,
contractAddress: null,
logs: [],
status: true,
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
rawLogs: []
,
logs: []
truffle(develop)> todo.changeTaskState(0)
tx: '0xddb313978411cd3f1429f1eb61b9bbde816e3a874d765aa5588a69508d226b44',
receipt:
transactionHash: '0xddb313978411cd3f1429f1eb61b9bbde816e3a874d765aa5588a69508d226b44',
transactionIndex: 0,
blockHash: '0xbae43abf22ca06de65a41e3e54493ad944f4b997b12a3ed407bc5f778d00a3be',
blockNumber: 6,
from: '0x3455100c0b0617afbf0f53db5e5c07366e20791b',
to: '0x645a78fe8eb3529291ba63a8e420d26c7baf61a0',
gasUsed: 45320,
cumulativeGasUsed: 45320,
contractAddress: null,
logs: [],
status: true,
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
rawLogs: []
,
logs: []
truffle(develop)> todo.getTasks()
[
[ 'Hello, world!', true, content: 'Hello, world!', completed: true ]
]
但是,当我尝试从网络应用程序调用这些合约的函数时,似乎与 Truffle 提供的本地区块链存在某种通信错误。
当然,我已经在浏览器中安装了 Metamask,并将其连接到 http://127.0.0.1:9545(正如 Truffle 在执行 truffle develop
命令时告诉我的那样)。我还导入了 Truffle 提供的私有短语,以便我可以访问该本地网络上的 10 个测试地址。
我还在build/contracts
目录中找到了合约的地址和ABI,并在React中设置了一个简单的前端。
import Web3 from 'web3';
import React, useState, useEffect from "react";
const TODO_ABI =
[
"inputs": [
"internalType": "string",
"name": "content",
"type": "string"
],
"name": "addTask",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
,
"inputs": [
"internalType": "uint256",
"name": "taskId",
"type": "uint256"
],
"name": "changeTaskState",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
,
"inputs": [
"internalType": "uint256",
"name": "taskId",
"type": "uint256"
,
"internalType": "string",
"name": "content",
"type": "string"
],
"name": "editTaskContent",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
,
"inputs": [],
"name": "getTasks",
"outputs": [
"components": [
"internalType": "string",
"name": "content",
"type": "string"
,
"internalType": "bool",
"name": "completed",
"type": "bool"
],
"internalType": "struct ToDo.Task[]",
"name": "",
"type": "tuple[]"
],
"stateMutability": "view",
"type": "function",
"constant": true
];
const TODO_ADDRESS = "0x645a78fe8eb3529291ba63a8e420d26c7baf61a0";
function ChangeTaskStateButton(props)
return (
<button onClick= () => props.contract.methods.changeTaskState(props.id).call() > props.state </button>
);
function Task(props)
return (
<li>
props.content | <ChangeTaskStateButton contract= props.contract id= props.id state= props.completed ? "Completed" : "Pending "></ChangeTaskStateButton>
</li>
);
function TasksList(props)
let tasks = [];
const tasksData = props.tasks;
for(let i = 0; i < tasksData.length; i++)
tasks.push(<Task id=i content= tasksData[i].content completed= tasksData[i].completed contract= props.contract ></Task>);
return (
<div>
<ul>
tasks
</ul>
</div>
);
function TaskForm(props)
const [content, setContent] = useState("");
const handleSubmit = (event) =>
event.preventDefault();
props.contract.methods.addTask(content).call()
.then(() => props.setTasks(props.tasks.concat(content: content, completed: false)));
;
const handleChange = (event) =>
setContent(event.target.value);
;
return(
<form onSubmit= handleSubmit >
<input type="text" onChange= handleChange ></input>
<button type="submit">Submit</button>
</form>
);
function App()
const [web3] = useState(new Web3(Web3.givenProvider || "http://localhost:9545"));
const [contract] = useState(new web3.eth.Contract(TODO_ABI, TODO_ADDRESS));
const [tasks, setTasks] = useState([]);
useEffect(() =>
contract.methods.getTasks().call()
.then(tasks =>
setTasks(tasks);
);
, [contract.methods]);
return (
<div>
<TaskForm contract=contract setTasks=setTasks tasks=tasks></TaskForm>
<TasksList tasks=tasks contract=contract></TasksList>
</div>
);
对getTasks()
的调用总是返回一个空数组,即使我通过终端添加了一个与当前在 Metamask 上使用的地址相同的任务,而对 addTask()
的调用不会在 smart 中存储任何内容合同地图。调用这两个函数不会导致浏览器控制台中出现任何错误或警告。但是,对changeTaskState()
的调用确实会导致显示两个错误:
inpage.js:1 MetaMask - RPC Error: Internal JSON-RPC error.
code: -32603, message: "Internal JSON-RPC error.", data: …
code: -32603
data: message: "VM Exception while processing transaction: revert", code: -32000, data: …
message: "Internal JSON-RPC error."
__proto__: Object
index.js:50 Uncaught (in promise) Error: Internal JSON-RPC error.
"message": "VM Exception while processing transaction: revert",
"code": -32000,
"data":
"0x359c33ac64b2b3eb0096b40b2d225679d4212f40fc86ef938af49fcc47159f2c":
"error": "revert",
"program_counter": 994,
"return": "0x4e487b710000000000000000000000000000000000000000000000000000000000000032"
,
"stack": "RuntimeError: VM Exception while processing transaction: revert\n at Function.RuntimeError.fromResults (C:\\Users\\gianm\\AppData\\Roaming\\npm\\node_modules\\truffle\\build\\webpack:\\node_modules\\ganache-core\\lib\\utils\\runtimeerror.js:94:1)\n at C:\\Users\\gianm\\AppData\\Roaming\\npm\\node_modules\\truffle\\build\\webpack:\\node_modules\\ganache-core\\lib\\blockchain_double.js:568:1",
"name": "RuntimeError"
at Object._fireError (index.js:50)
at sendTxCallback (index.js:540)
at cb (util.js:689)
at callbackifyOnRejected (util.js:666)
at Item.push../node_modules/process/browser.js.Item.run (browser.js:153)
at drainQueue (browser.js:123)
我也尝试使用 Ganache,而不是 Truffle 的内置本地区块链,我什至尝试更换浏览器,但似乎没有任何效果。我还检查了 Metamask 是否真的连接到 webapp 并且确实是。我在这里错过了什么?
【问题讨论】:
【参考方案1】:与智能合约交互的主要方式有两种。一个call(只读,免费)和一个transaction(读写,需要gas费)。
您的 React 代码对 addTask()
和 changeTaskState()
使用了 .call()
方法,这不允许在合约存储中写入。
由于您正在使用 MetaMask,因此您应该使用 Ethereum Provider API 并向 MM 提交request,这将要求用户确认交易。
因此,您可以获取data
字段的内容,而不是props.contract.methods.addTask(content).call()
,然后生成交易请求。
const data = props.contract.methods.addTask(content).encodeABI();
ethereum.request(
method: 'eth_sendTransaction',
params: [
from: senderAddress,
to: contractAddress,
data: data
],
);
注意:您可以在connecting to MM之后设置senderAddress
。
另一种方法是使用 web3 .send()
方法,但这需要将发送者的私钥传递给 web3
(在前端 JS 应用中是个坏主意)或解锁的提供者帐户(本地提供者通常很少有解锁帐户,但生产的没有)。
【讨论】:
感谢您的帮助!我有点着急,所以我刚刚快速测试了 send() 解决方案,它确实可以通过明确地将请求交易的公共地址传递给它(以对象'from: “0x0”'),这很棒。我不明白你所说的“但这需要将发件人的私钥传递给 web3”是什么意思,因为在我看来,我只是在指定公钥。我错了吗?此外,Ethereum Provider API 是否仅适用于 Metamask?使用这些是不是更方便? 如果您只指定了地址并提交了交易,这意味着该帐户(具有指定地址)在提供商(在您的情况下为 Truffle)已解锁。如果您使用生产提供程序(例如 Infura)执行此操作,则需要将私钥传递给 web3,因为该帐户不会在提供程序上解锁...... Ethereum Provider API (link to EIP) 是一个界面。所以任何钱包软件都可以实现它。但 MetaMask 只是实现它的最广泛使用的钱包。 所以以太坊提供者 API 是当前的标准,使用这些是实现前端接口和智能合约之间交互的正确方法。基本上 web3 api 需要一个帐户的私钥,我猜是为了签署它请求合同的交易,而这些 API 根本不需要。我不完全理解的最后一件事是为什么 Ethereum Provider API 不需要那种信息。是不是因为钱包(MM或其他)知道私钥并代表用户签署交易? “钱包知道私钥” - 完全正确。当用户在 MM 中点击“提交”按钮时,它使用私钥(存储在 MM 中)对交易进行签名。以上是关于Web 应用程序无法与通过 Truffle 部署的以太坊智能合约正确通信的主要内容,如果未能解决你的问题,请参考以下文章