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

如何获取NodeJS VM执行后的状态字节码?JavaScript智能合约实验【更新版】

如何从Node.js VM实例中获取包含新状态的字节码?

我明白你现在在尝试用Node.js的VM模块模拟以太坊智能合约的运行流程,核心诉求是——执行完修改状态的调用后,怎么把包含新状态的合约导出成字节码,传给下一个VM实例继续用。先给你理清楚问题所在,再给两个可行的解决方案:

先说说当前代码的核心问题

你现在的代码里,compileCode生成的字节码本质是JS代码的编译缓存,它只保存代码本身的编译结果,完全不包含运行时的状态(比如你设置的name变量)。而runBytecode每次调用都是在同一个全局上下文里执行,状态确实会被修改,但这些状态是存在内存的上下文里,根本不在字节码里,所以你没法从原来的bytecode变量里拿到更新后的状态。

两种解决方案,推荐第一种(更贴合以太坊逻辑)

方案1:状态与代码分离,序列化状态传递

这和以太坊的设计思路一致:合约代码是固定不变的,状态单独存储,每次调用合约是用代码去操作状态。我们可以把合约逻辑封装成对象方法,状态存在上下文对象里,执行后把状态序列化,传给下一个VM实例时再恢复。

完整代码实现:

const vm = require('vm');
const v8 = require('v8');
v8.setFlagsFromString('--no-lazy');
if (Number.parseInt(process.versions.node.split('.')[0], 10) >= 12) {
  v8.setFlagsFromString('--no-flush-bytecode');
}

// 编译合约逻辑(仅代码,不含状态)
const compileContract = function (contractCode) {
  if (typeof contractCode !== 'string') {
    throw new Error(`contractCode must be string. ${typeof contractCode} was given.`)
  }
  let script = new vm.Script(contractCode)
  return script.createCachedData();
}

// 加载合约到新上下文,注入初始状态,返回带合约方法的上下文
const loadContract = function (bytecodeBuffer, initialState = {}) {
  if (!Buffer.isBuffer(bytecodeBuffer)) {
    throw new Error(`bytecodeBuffer must be a buffer object.`);
  }
  let length = readSourceHash(bytecodeBuffer);
  let dummyCode = "";
  if (length > 1) {
    dummyCode = '"' + "\u200b".repeat(length - 2) + '"';
  }
  // 创建独立上下文,避免污染全局
  const context = vm.createContext({...initialState});
  let script = new vm.Script(dummyCode, { cachedData: bytecodeBuffer });
  if (script.cachedDataRejected) {
    throw new Error('Invalid or incompatible cached data (cachedDataRejected)');
  }
  script.runInContext(context);
  return context;
}

// 执行调用代码,传入当前上下文,返回更新后的上下文
const executeCall = function (callCode, currentContext) {
  const context = vm.createContext({...currentContext});
  const callScript = new vm.Script(callCode);
  callScript.runInContext(context);
  return context;
}

const readSourceHash = function (bytecodeBuffer) {
  if (!Buffer.isBuffer(bytecodeBuffer)) {
    throw new Error(`bytecodeBuffer must be a buffer object.`)
  }
  return bytecodeBuffer.slice(8, 12).reduce((sum, number, power) => sum += number * Math.pow(256, power), 0)
}

// 合约逻辑:用this来维护状态,和代码逻辑分离
const smartContractCode = `
this.setName = function(_name){
  this.names = this.names || [];
  this.names.push(_name);
  console.log('Added name:', _name);
}
this.getName = function(){
  console.log('Current names:', this.names || []);
  return this.names || [];
}
`

// 流程演示
// 1. 编译合约逻辑成字节码(只做一次,因为代码不变)
const contractBytecode = compileContract(smartContractCode);

// 2. 初始化上下文,加载合约
let currentContext = loadContract(contractBytecode);

// 3. 执行两次添加名称的调用,更新状态
currentContext = executeCall("setName('Satoshi')", currentContext);
currentContext = executeCall("setName('Vitalik')", currentContext);

// 4. 验证当前状态
executeCall("getName()", currentContext);

// 5. 序列化状态,传给下一个VM实例
const serializedState = JSON.stringify(currentContext);

// 模拟新VM实例加载
const newInitialState = JSON.parse(serializedState);
const newContext = loadContract(contractBytecode, newInitialState);
executeCall("getName()", newContext); // 会输出 ['Satoshi', 'Vitalik']

方案2:把状态嵌入代码重新编译(不推荐)

如果你一定要把状态包含在字节码里,可以每次执行后把当前状态和合约代码拼接成新的代码,重新编译成字节码。但这种方式会让字节码越来越大,而且状态硬编码后不够灵活,只适合简单场景。

示例片段:

// 生成包含当前状态的新合约代码
const getContractWithState = (originalCode, state) => {
  // 把状态变量定义放在代码最前面
  const stateDefinitions = Object.entries(state)
    .map(([key, value]) => `let ${key} = ${JSON.stringify(value)};`)
    .join('\n');
  return `${stateDefinitions}\n${originalCode}`;
}

// 执行调用后,生成带状态的新代码并重新编译
const newContractCode = getContractWithState(smartContract, currentContext);
const newBytecode = compileCode(newContractCode);

// 下一个VM实例运行这个新字节码就能拿到状态
runBytecode(newBytecode);
runBytecode(compileCode("getName()")); // 能获取到之前设置的名称

最后再强调下关键逻辑

Node.js VM的cachedData(也就是你生成的bytecode)只存储代码的编译结果,和运行时状态完全无关。状态是保存在执行上下文(context)里的,这和以太坊的模式一致:合约字节码不可变,状态单独存储,调用合约只是用字节码去修改状态,而不是修改字节码本身。所以方案1才是更合理的选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 08:07:42