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 部署的以太坊智能合约正确通信的主要内容,如果未能解决你的问题,请参考以下文章

以太坊——运用truffle框架部署第一个DAPP ---- Pet-Shop

Truffle 合约验证无法在 BSC 测试网上运行

验证使用 Truffle 部署的智能合约代码

Truffle详解

以太坊:Truffle框架入门之运行MetaCoin

以太坊 DApp 开发入门实战! 用Node.js和truffle框架搭建——区块链投票系统!