You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

智能合约方法在Remix运行正常但Ganache中触发回退如何解决

概述

我正在完成区块链开发者课程的项目,项目必须使用Ganache测试,因为课程评审团队将使用Ganache核验成果。
我当前开发的智能合约包含两个独立合约:一个负责数据存储,另一个负责应用层用户交互。用户调用registerOracle方法时,合约要求支付1 ether以生成1个oracle,该oracle将拥有1个ID和3个索引。
我在Remix上测试合约一切正常,但在Ganache中测试oracle生成功能时触发回退错误。

项目地址

项目文件存放在Github仓库中,同时上传了Remix测试操作流程、Ganache报错信息的相关截图。
代码体量较大,我将在下方贴出报错相关的核心代码,完整代码可查看仓库内容。

TestingEnv.js 文件

const Web3 = require('web3');
const fs = require('fs').promises;

async function main () {
    /**
    * 以下部分代码逻辑无问题可忽略
    */
    const web3 = new Web3(new Web3.providers.WebsocketProvider("HTTP://127.0.0.1:8545"));

    const appABI = (JSON.parse(await fs.readFile("./bin/appAbi.json", "utf8"))).abi;
    const appBYTECODE = (JSON.parse(await fs.readFile("./bin/appByteCode.json", "utf8"))).bytecode;

    const dataABI = (JSON.parse(await fs.readFile("./bin/dataAbi.json", "utf8"))).abi;
    const dataBYTECODE = (JSON.parse(await fs.readFile("./bin/dataByteCode.json", "utf8"))).bytecode;

    const owner = (await web3.eth.getAccounts())[0];

    const appContract = await new web3.eth.Contract(appABI).deploy({data: '0x' + appBYTECODE})
    .send({from: owner, gasLimit: 6000000, gasPrice: web3.utils.toWei('5', 'gwei')});

    const dataContract = await new web3.eth.Contract(dataABI).deploy({data: '0x' + dataBYTECODE})
    .send({from: owner, gasLimit: 6000000, gasPrice: web3.utils.toWei('5', 'gwei')});

    const appAddress = appContract._address;
    const dataAddress = dataContract._address;

    await appContract.methods.registerDataContract(dataAddress).send({from: owner});
    await dataContract.methods.registerApplicationContract(appAddress).send({from: owner});
    /**
    * 可忽略代码部分结束
    */

    /**
    * 回退错误触发位置
    */
    const oracleFee = await web3.utils.toWei("1", "ether");
    const server = (await web3.eth.getAccounts())[2];
    // 我需要注册20个oracle
    for (var i=0; i<20; i++) {
        await appContract.methods.registerOracle().send({from: server, value: oracleFee});
        await new Promise(resolve => setTimeout(resolve, 2000));
    };
    const totalOracles = await appContract.methods.getTotalOracles().call({from: server});
    console.log(`Total Numbers of Oracles Registered: ${totalOracles}`);
    for (var i = 0; i<totalOracles; i++) {
        var indexes = await appContract.methods.getOraceIndexes(i).call({from: server});
        if (i >= 0 && i <= 9) {
            console.log(`Oracle 0${i} indexes: ${indexes._index1.toString()} ${indexes._index2.toString()} ${indexes._index2.toString()}`);
        } else {
            console.log(`Oracle ${i} indexes: ${indexes._index1.toString()} ${indexes._index2.toString()} ${indexes._index2.toString()}`);
        };
    };
};

main();

FlightSuretyApp.sol 合约代码

pragma solidity ^0.4.24;

interface iFlightSuretyData {
    function registerOracle(address server, uint8 index1, uint8 index2, uint8 index3) external payable;
}

import "./SafeMath.sol";

contract FlightSuretyApp {
    using SafeMath for uint256;

    bool operationalStatus;
    address owner;
    iFlightSuretyData FlightSecuretyData;

    constructor() public {
        operationalStatus = true;
        owner = msg.sender;
    }

    modifier requireOwner() {
        require(msg.sender == owner, "Owner is required");
        _;
    }

    modifier requireOperational() {
        require(operationalStatus == true, "Contract is not operational");
        _;
    }

    function registerDataContract(address dataContract) external
    requireOwner {
        FlightSecuretyData = iFlightSuretyData(dataContract);
    }

    uint256 constant ORACLE_REGISTRATION_FEE = 1 ether;

    modifier requireOracleRegistrationFee() {
        require(msg.value == ORACLE_REGISTRATION_FEE, "Oracle Registration Cost 1 ether");
    _;
    }

    function registerOracle() external payable
    requireOperational
    requireOracleRegistrationFee {
        (uint8 index1, uint8 index2, uint8 index3) = indexesThrown();
        FlightSecuretyData.registerOracle.value(msg.value)(msg.sender, index1, index2, index3);
    }

    function indexesThrown() private view returns(uint8 _index1, uint8 _index2, uint8 _index3)                
    {
        uint8 index1 = generateIndex1();
        uint8 index2 = generateIndex2(index1);
        uint8 index3 = generateIndex3(index1, index2);
        return (index1, index2, index3);
    }

    function generateIndex1() private view returns(uint8 _index) {
        uint256 mod = 10;
        uint256 time = block.timestamp;
        uint256 difficulty = block.difficulty;
        uint8 value = uint8(SafeMath.mod(uint256(keccak256(abi.encodePacked(time, difficulty, msg.sender))), mod));
        return value;
    }

    function generateIndex2(uint8 _index1) private view returns(uint8 _index) {
        uint256 mod = 10;
        uint256 time = block.timestamp;
        uint256 difficulty = block.difficulty;
        uint8 value = uint8(SafeMath.mod(uint256(keccak256(abi.encodePacked(time, difficulty, msg.sender))), mod));
        while(value == _index1) {
            time = SafeMath.add(time, 500);
            difficulty = SafeMath.add(difficulty, 700);
            value = uint8(SafeMath.mod(uint256(keccak256(abi.encodePacked(time, difficulty, msg.sender))), mod));
        }
        return value;
    }

    function generateIndex3(uint8 _index1, uint8 _index2) private view returns(uint8 _index) {
        uint256 mod = 10;
        uint256 time = block.timestamp;
        uint256 difficulty = block.difficulty;
        uint8 value = uint8(SafeMath.mod(uint256(keccak256(abi.encodePacked(time, difficulty, msg.sender))), mod));
        while((value == _index1) || (value == _index2)) {
            time = SafeMath.add(time, 500);
            difficulty = SafeMath.add(difficulty, 700);
            value = uint8(SafeMath.mod(uint256(keccak256(abi.encodePacked(time, difficulty, msg.sender))), mod));
        }
        return value;
    }
}

FlightSuretyData.sol 合约代码

import "./SafeMath.sol";

contract FlightSuretyData {
    using SafeMath for uint256;

    bool operationalStatus;
    address owner;
    address appContract;

    constructor() public {
        operationalStatus = true;
        owner = msg.sender;
        airlines[owner].registrationStatus = true;
    }

    modifier requireOwner() {
        require(msg.sender == owner, "Require contract owner");
        _;
    }

    modifier requireApplication() {
        require(msg.sender == appContract, "Require application");
        _;
    }

    modifier requireOperational() {
        require(operationalStatus == true, "Contract is not operational");
        _;
    }

    function registerApplicationContract(address application) external
    requireOwner
    requireOperational {
        appContract = application;
    }

    struct oracle {
        uint8 index1;
        uint8 index2;
        uint8 index3;
        mapping(bytes32 => bool) voteState;
    }

    struct oracleServer {
        mapping(uint256 => oracle) oracles;
        uint256 numberOfOracles;
    }

    mapping(address => oracleServer) private oracleServers;

    function registerOracle(address server, uint8 index1, uint8 index2, uint8 index3) external payable
    requireOperational
    requireApplication {
        uint256 counter = oracleServers[server].numberOfOracles;
        oracleServers[server].oracles[counter].index1 = index1;
        oracleServers[server].oracles[counter].index2 = index2;
        oracleServers[server].oracles[counter].index3 = index3;
        counter = SafeMath.add(counter, 1);
        oracleServers[server].numberOfOracles = counter;
    }
}

项目运行方式

安装依赖后直接运行TestingEnv.js文件即可复现问题。

运行环境配置

  • Windows 10 Home 21H1版本
  • Ganache 2.5.4版本(使用Ganache官方桌面客户端,非依赖包版本)
  • Node 11.0.0版本
  • Solidity 0.4.24版本

Remix运行正常证明

Remix测试成功截图

Ganache报错证明

Ganache报错截图

问题咨询

我已测试合约其他功能均运行正常,仅oracle注册功能触发报错导致项目进度停滞,请问该问题如何排查解决?我遗漏了哪些配置或逻辑?


问题排查与解决方法

1. 核心触发原因:Ganache出块规则差异

Remix测试环境是每笔交易单独生成一个区块,而你本地Ganache默认启用自动挖矿,你代码中每次注册oracle后添加的2秒延迟不会触发新块生成,导致20次循环调用的block.timestamp、block.difficulty完全一致,加上调用地址固定为同一个server,所以第一次生成三个不同索引后,后续调用时generateIndex2、generateIndex3的while循环会始终生成和第一个索引重复的值,进入死循环耗尽gas触发回退。
修复方案:要么修改索引生成逻辑,新增随调用次数递增的随机种子参数,不再完全依赖区块变量;要么在Ganache设置中将自动挖矿改为固定1秒间隔出块;要么去掉代码中的2秒延迟,让多笔交易尽可能打包到不同区块。

2. 隐藏问题1:Data合约无法接收ETH

你在调用FlightSecuretyData.registerOracle时向Data合约转入了1 ETH,但FlightSuretyData没有实现payable fallback函数,Ganache的严格校验模式会直接拒绝合约收款操作,触发回退。
修复方案:在FlightSuretyData合约中添加fallback函数:

function() external payable {}

或者调整逻辑,将注册费直接存在App合约中,不向Data合约转账。

3. 隐藏问题2:索引生成逻辑低概率冲突

当前索引生成逻辑是从0-9中取3个不重复的值,当同一个地址注册超过12次时,所有索引组合已经被穷尽,必然进入死循环,建议增加随机种子维度,或者允许同一地址重复使用索引组合。

内容的提问来源于stack exchange,提问作者Jask_Skull

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 19:39:02