Blog

Our Experience in Migration from Ethermint to EVMOS

image

Vitaliy Obrevko

Time to read

7 min

July 23, 2024

In the ever-evolving world of blockchain technology, staying up-to-date with the latest advancements is crucial for maintaining efficiency and reliability. Our recent project faced significant challenges due to an outdated EVM-based blockchain Ethermint. To address these issues, we decided to transition to a new blockchain solution based on Evmos. This article outlines our journey, from identifying the initial problems to achieving a seamless transition with 100% uptime.


Problem with Ethermint

Our project initially relied on an Ethermint-based blockchain, which, while functional, proved to be increasingly unreliable. Approximately every 2-3 months, the blockchain would shut down unexpectedly, necessitating manual restarts of the nodes. This issue disrupted our operations and posed a significant risk to user satisfaction and trust.

Given the complete lack of activity from the Ethermint team and the availability of alternatives, it became evident that continuing with the existing Ethermint-based blockchain was unsustainable in the long term.

Decision to Switch to EVMOS

After thorough research and consideration, we decided to switch to a blockchain solution based on the next version of Ethermint – EVMOS. EVMOS aims for compatibility with EVM / smart contracts. So if you already have an EVM application running on an Ethereum or EVM-compatible chain you can easily migrate to EVMOS and offer your app to the Cosmos ecosystem.

The decision was driven by several factors, including EVMOS's improved architecture and compatibility with our existing systems. We anticipated a smoother operation and fewer disruptions, which were critical for maintaining user trust and operational efficiency. The planning phase involved detailed evaluations and risk assessments to ensure a well-informed transition.


Handling the Nonce Issue

*A nonce refers to a number or value that can only be used once. Nonces are often used on authentication protocols and cryptographic hash functions. In the context of blockchain technology, a nonce refers to a pseudo-random number that is utilized as a counter during the process of mining.

One of the most significant challenges we faced during the transition was the issue with Nonce values. The Nonce values in the new EVMOS blockchain can’t match those in the old Ethermint blockchain. This discrepancy posed a major hurdle since some of the client data and functions on the backend relied on this value.

The solution to this problem is to completely decouple the database from this value and bind it to the block numbers (which in truth should have been done initially)

Transition Phase

We executed a script that saved the state of all issued tokens, user balances, and the state of smart contracts.

const pullAccounts = async (db: DB) => {
  console.log('Pulling accounts...')
  const accounts = await db.select().from(wallet_account);
  const addresses = accounts.map(a => a.address);
  const dataDir = path.join(__dirname, '../data', ENV, 'accounts.json');
  await fs.writeFile(dataDir, JSON.stringify(addresses, null, 2));
  console.log(`Pulled ${addresses.length} accounts`);
}

const pullProjects = async (db: DB) => {
  console.log('Pulling projects...');
  const projects = await db.select().from(project);
  const ids = projects.map(p => p.nft_contract_id);
  const txs = await db.select().from(transaction).where(inArray(transaction.id, ids));
  const dataDir = path.join(__dirname, '../data', ENV, 'projects.json');
  await fs.writeFile(dataDir, JSON.stringify(txs, null, 2));
  console.log(`Pulled ${txs.length} projects`);
}

const pullTokens = async (db: DB) => {
  console.log('Pulling tokens...');
  const tokens = await db.select().from(token);
  const ids = tokens.map(t => t.blockchain_reference);
  const txs = await db.select().from(transaction).where(inArray(transaction.id, ids.map(Number)));
  const dataDir = path.join(__dirname, '../data', ENV, 'tokens.json');
  await fs.writeFile(dataDir, JSON.stringify(txs, null, 2));
  console.log(`Pulled ${txs.length} tokens`);
}

const main = async () => {
  console.log(`Pulling ${ENV} data...`);
  const {db, close} = await getDb();
  await pullAccounts(db);
  await pullProjects(db);
  await pullTokens(db);
  console.log('Done');
  close();
};

At this point, the new Nodes were already ready to launch.

The next part was the production of the genesis block in the new blockchain. We wanted to immediately launch a new blockchain from the moment where the old one left off, but the problem of linking the backend to the blocks still remained.

The way out of this situation was to simulate the minimum number of transactions in separate blocks. Luckily we only had 2,253 transactions to complete.

An example of a script that deploys contracts and transfers tokens in batch

const provider = new ethers.JsonRpcProvider('http://160.90.206.47:8545');
const admin = new ethers.Wallet('3AEAFFCOOAE4983C5B9E2B4CEC570C65A3160678B9BDD5CEB3D8BE503198B85E', provider);

const batchTransfer = async () => {
  const addresses = await fs.readFile(path.join(__dirname, `../data/${ENV}/accounts.json`), 'utf-8');
  const batchTransfer = new ethers.Contract(batchTransferAddress, BatchTransferABI, admin);
  await batchTransfer.batchTransfer(JSON.parse(addresses), { gasPrice: 0, value: addresses.length });
  console.log('Addresses topped up');
}

const deployProjects = async () => {
  const projects = JSON.parse(await fs.readFile(path.join(__dirname, `../data/${ENV}/projects.json`), 'utf-8'));

  const p = projects[1];

  const data = p.payload.replace('02f93ff48205398205038080832e97488080b93f9f', '')

  const tx = await admin.sendTransaction({
    value: 0,
    data,
    gasLimit: 15000000,
    gasPrice: 0,
  })

  await tx.wait();

  const receipt = await provider.getTransactionReceipt(tx.hash);
  const newContractAddress = receipt.contractAddress;

  console.log(newContractAddress);
  p.newContractAddress = newContractAddress;

  await fs.writeFile(path.join(__dirname, `../data/${ENV}/projects.json`), JSON.stringify(projects, null, 2));
}

const deployTokens = async () => {
  const tokens = JSON.parse(await fs.readFile(path.join(__dirname, `../data/${ENV}/tokens.json`), 'utf-8'));

  for (const t of tokens) {
    const data = '0x' + t.payload.slice(44);
    const tx = await admin.sendTransaction({
      value: 0,
      data,
      gasLimit: 15000000,
      gasPrice: 0,
    })
    await tx.wait();

    const receipt = await provider.getTransactionReceipt(tx.hash);
    const newContractAddress = receipt.contractAddress;

    console.log(newContractAddress);
    t.newContractAddress = newContractAddress;
    await fs.writeFile(path.join(__dirname, `../data/${ENV}/tokens.json`), JSON.stringify(tokens, null, 2));
  }
}
const main = async () => {
  await batchTransfer();
  await deployProjects();
  await deployTokens();
}
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });


Setting up the new blockchain was straightforward, and early tests indicated that everything was functioning as expected. Considering that, EVMOS was created with the expectation of easy transfer from any other EVM-based blockchain.

However, as we delved deeper into the integration process, we encountered several specific challenges unique to our project's requirements.

Ensuring a Seamless User Experience

A primary goal of our transition was to make it invisible to users, ensuring that they experienced no disruptions or changes in functionality. Achieving this required meticulous planning and execution. We employed several strategies to ensure a seamless user experience, including extensive testing, phased rollouts, and continuous monitoring.

Our approach focused on maintaining the integrity of user data and interactions throughout the transition. By simulating real-world scenarios and conducting rigorous testing, we were able to identify and address potential issues before they impacted users. This proactive approach was crucial in achieving a smooth transition and maintaining user confidence.


Achieving 100% Uptime

When all challenges were addressed, we successfully replaced the old blockchain with the new Evmos-based solution. Since the transition, we have achieved 100% uptime, a significant improvement from the frequent shutdowns experienced with the old system.

To maintain this high level of reliability, we implemented robust monitoring and maintenance practices. Continuous performance monitoring allows us to detect and address potential issues proactively, ensuring uninterrupted service for our users. The success of this transition has reinforced the importance of selecting the right blockchain solution and the value of thorough planning and testing.