Solana Primer: Comparisons with Ethereum EVM Methods

A comprehensive comparison between Ethereum's EVM-based RPC Methods and Solana's RPC calls. Understand the nuances, similarities, and distinctions as we bridge the knowledge gap between these two leading blockchains.

Solana Primer: Comparisons with Ethereum EVM Methods

Diving into the expansive universe of blockchain, many developers familiar with Ethereum's RPC methods often seek to understand how these methods translate to other blockchains, such as Solana. This primer compares Ethereum EVM-based RPC Methods with Solana RPC Methods, shedding light on their respective functionalities and equivalences. Whether you're an Ethereum veteran seeking clarity on Solana's workings or a developer venturing into the world of Solana, this primer promises insights that will augment your blockchain knowledge.

Below is a list of EVM-based RPC Methods. What are the equivalent RPC calls for Solana?

eth_getBlockByNumber

The equivalent Solana RPC method for the Ethereum method eth_getBlockByNumber would be the Solana RPC method getBlock. However, it's important to note that Solana and Ethereum have different structures and terminologies when it comes to their blockchains, so there might be some differences in the data returned and the way you interact with these methods.

In Ethereum, eth_getBlockByNumber allows you to retrieve a block's information using its block number or block tag. Similarly, in Solana, getBlock allows you to retrieve block information by providing the block's slot (which is analogous to the block number in Ethereum).

Here's an example of how you might use the getBlock method in Solana:

# Using solana-cli
solana block <block_slot>

If you're interacting with the Solana RPC API programmatically, you can use a library like solana-web3.js for JavaScript to call this method:

const web3 = require('@solana/web3.js');

const solanaRpcUrl = 'QUICKNODE_URL'; // Replace with the quicknode url
const connection = new web3.Connection(solanaRpcUrl, 'confirmed');

async function getSolanaBlock(slot) {
  const block = await connection.getBlock(slot);
  console.log(block);
}

const blockSlot = 12345678; // Replace with the desired block slot
getSolanaBlock(blockSlot);

eth_getTransactionReceipt

The Ethereum method eth_getTransactionReceipt retrieves the receipt of a transaction, including information about contract events and logs. In Solana, there isn't a direct equivalent method because Solana and Ethereum have different transaction models and data structures. However, you can achieve similar functionality using a combination of Solana's RPC methods and programming features.

To get similar information as eth_getTransactionReceipt in Solana, you would need to follow these steps:

  1. Use Solana's RPC Methods to Get Transaction Information:
  2. Use the getTransaction Solana RPC method to retrieve transaction details.
  3. Decode Logs and Events:
  4. Solana doesn't inherently have the concept of logs like Ethereum. Instead, you would need to design your Solana program to emit custom events or data that you're interested in tracking.
  5. Parse and decode these custom events from the transaction's transaction message or transaction logs.
  6. Construct a Custom Response:
  7. Based on the decoded events and information, you can construct a custom response object that resembles the information you would get from an Ethereum transaction receipt.

Here's a simplified example of how you might approach this using JavaScript and Solana's @solana/web3.js library:

const web3 = require('@solana/web3.js');

async function getSolanaTransactionReceipt(transactionSignature) {
  const solanaRpcUrl = 'QUICKNODE_URL'; // Replace with the quicknode url
  const connection = new web3.Connection(solanaRpcUrl, 'confirmed');

  const transaction = await connection.getTransaction(transactionSignature);

  if (!transaction) {
    return null; // Transaction not found
  }

  // Parse and decode custom events/logs from transaction.message
  const decodedEvents = parseAndDecodeEvents(transaction.message.logs);

  const receipt = {
    transactionHash: transactionSignature,
    blockNumber: transaction.slot, // Solana's equivalent of block number
    // Other relevant information based on your decoded events
    decodedEvents,
  };

  return receipt;
}

function parseAndDecodeEvents(logs) {
  // Implement logic to parse and decode logs into custom event data
  // This could involve iterating through logs and extracting data
  // Refer to Solana's documentation and your program's event structure

  return decodedEvents;
}

const transactionSignature = '...'; // Replace with the Solana transaction signature
getSolanaTransactionReceipt(transactionSignature)
  .then((receipt) => {
    console.log(receipt);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

Remember that Solana's design and data structures are different from Ethereum's, so you'll need to adjust your approach accordingly. The example provided is a high-level guideline, and you'll need to customize it based on your specific Solana program and use case.

eth_getCode

The Ethereum method eth_getCode is used to retrieve the bytecode of a smart contract deployed on the Ethereum blockchain. The comparable Solana RPC method for this is getAccountInfo, which can be used to retrieve information about an account, including the data associated with it.

Here's how you can use the Solana getAccountInfo method in comparison to Ethereum's eth_getCode:

// Using web3.js for Ethereum
const Web3 = require('web3');
const web3 = new Web3('QUICKNDOE_URL'); // Replace with the quicknode url

async function getEthContractBytecode(address) {
  const bytecode = await web3.eth.getCode(address);
  return bytecode;
}

const contractAddress = '0x...'; // Replace with the Ethereum contract address
getEthContractBytecode(contractAddress)
  .then((bytecode) => {
    console.log(bytecode);
  })
  .catch((error) => {
    console.error('Error:', error);
  });
const web3 = require('@solana/web3.js');

async function getSolanaContractData(address) {
  const solanaRpcUrl = 'QUICKNODE_URL'; // Replace with the quicknode url  const connection = new web3.Connection(solanaRpcUrl, 'confirmed');

  const publicKey = new web3.PublicKey(address);
  const accountInfo = await connection.getAccountInfo(publicKey);

  if (!accountInfo) {
    return null; // Account not found
  }

  const contractData = accountInfo.data;
  return contractData;
}

const contractAddress = '...'; // Replace with the Solana contract address
getSolanaContractData(contractAddress)
  .then((contractData) => {
    console.log(contractData);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

In the Solana example, getAccountInfo retrieves information about the specified account, and the data field of the account info object contains the bytecode or data associated with the contract. Keep in mind that Solana accounts can hold various types of data, not just bytecode, so the contents of the data field might need further processing based on your specific use case.

Adjust the examples according to your use case and the libraries you're using. Solana's design and terminology differ from Ethereum's, so the way you handle contracts and their data will have some distinctions.

eth_call

The Ethereum method eth_call is used to execute a call to a contract's function or to retrieve contract state without making an actual transaction on the Ethereum network. The comparable Solana RPC method for this is simulateTransaction, which allows you to simulate a transaction and obtain the result of executing a program (smart contract) on the Solana blockchain.

Here's how you can use the Solana simulateTransaction method in comparison to Ethereum's eth_call:

// Using web3.js for Ethereum
const Web3 = require('web3');
const web3 = new Web3('QUICKNODE_URL');  // Replace with the quicknode url 

async function ethCall(contractAddress, data) {
  const result = await web3.eth.call({
    to: contractAddress,
    data: data,
  });
  return result;
}

const contractAddress = '0x...'; // Replace with the Ethereum contract address
const inputData = '0x...'; // Replace with the input data for the function call
ethCall(contractAddress, inputData)
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error('Error:', error);
  });
const web3 = require('@solana/web3.js');

async function simulateSolanaTransaction(sender, programId, data) {
  const solanaRpcUrl = 'QUICKNODE_URL'; // Replace with the quicknode url  
  const connection = new web3.Connection(solanaRpcUrl, 'confirmed');

  const instruction = new web3.TransactionInstruction({
    keys: [{ pubkey: sender, isSigner: true, isWritable: true }],
    programId,
    data,
  });

  const simulatedTransactionResponse = await connection.simulateTransaction(
    new web3.Transaction().add(instruction)
  );

  const simulatedResult = simulatedTransactionResponse.value;
  return simulatedResult;
}

const senderPublicKey = new web3.PublicKey('...'); // Replace with the sender's public key
const programId = new web3.PublicKey('...'); // Replace with the Solana program ID
const inputData = Buffer.from('...'); // Replace with the input data for the program
simulateSolanaTransaction(senderPublicKey, programId, inputData)
  .then((simulatedResult) => {
    console.log(simulatedResult);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

In the Solana example, simulateTransaction allows you to simulate executing a program (smart contract) using a transaction instruction. Keep in mind that the setup of Solana programs and transactions is different from Ethereum's, so you'll need to adapt your code accordingly. Solana's architecture and design may require additional considerations and adjustments compared to Ethereum's EVM-based approach.

eth_getBalance

The Ethereum method eth_getBalance is used to retrieve the balance of a specific Ethereum address. The comparable Solana RPC method for this is getBalance, which allows you to retrieve the balance of a specific Solana wallet address.

Here's how you can use the Solana getBalance method in comparison to Ethereum's eth_getBalance:

// Using web3.js for Ethereum
const Web3 = require('web3');
const web3 = new Web3('QUICKNODE_URL'); // Replace with the quicknode url

async function getEthBalance(address) {
  const balance = await web3.eth.getBalance(address);
  return balance;
}

const ethAddress = '0x...'; // Replace with the Ethereum address
getEthBalance(ethAddress)
  .then((balance) => {
    console.log(balance);
  })
  .catch((error) => {
    console.error('Error:', error);
  });
const web3 = require('@solana/web3.js');

async function getSolanaBalance(address) {
  const solanaRpcUrl = 'QUICKNODE_URL'; // Replace with the quicknode url 
  const connection = new web3.Connection(solanaRpcUrl, 'confirmed');

  const publicKey = new web3.PublicKey(address);
  const balance = await connection.getBalance(publicKey);
  return balance;
}

const solanaAddress = '...'; // Replace with the Solana wallet address
getSolanaBalance(solanaAddress)
  .then((balance) => {
    console.log(balance);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

In both cases, you're retrieving the balance of a specific address, but keep in mind that the address formats and network-specific details will differ between Ethereum and Solana. Solana uses the Ed25519 public key format for addresses, whereas Ethereum uses the hexadecimal format.

Please replace the placeholders with actual addresses and adapt the code according to the libraries and tools you are using. Solana and Ethereum have different architectures and designs, so the code and approach might vary in certain aspects.

debug_traceBlockByHash

Solana's architecture and design are quite different from Ethereum's, and while Ethereum provides the debug_traceBlockByHash method to trace and debug blocks, Solana's RPC methods and tools might not have a direct equivalent with the same level of detail.

In Ethereum, debug_traceBlockByHash allows for detailed tracing and analysis of the execution and state changes within a block. Solana, on the other hand, primarily focuses on high throughput and performance, which might lead to differences in how debugging and tracing are implemented.

Solana's RPC methods does not offer a direct equivalent for tracing blocks with the same level of detail as Ethereum's debug_traceBlockByHash. Solana's design prioritizes speed and scalability, and its focus on parallel processing might make tracing every detail in a block less practical.

However, for debugging and tracing purposes, you might find the following Solana tools and methods useful:

  • Transaction Explorer: Solana provides a transaction explorer on its block explorer websites (e.g., Solana Explorer). While it might not offer the same level of detail as Ethereum's block tracing, it can help you visualize transactions and program execution within blocks.
  • Solana CLI and Logging: The Solana Command-Line Interface (CLI) provides commands to interact with the Solana network. While it might not provide the same tracing capability as Ethereum's debugging methods, it does offer tools for monitoring transactions and program logs.
  • Program Logs: Solana programs can emit logs, and these logs can be retrieved using the getLogs RPC method. These logs can provide insights into the execution of Solana programs.
  • Developer Community: As Solana's ecosystem evolves, new tools and techniques might emerge for debugging and tracing. It's recommended to follow Solana's official channels and developer community discussions for updates and best practices.

For trace calls does the concept of cross-program invocations return the same data for internal transactions?

In Solana, the concept of cross-program invocations is similar to internal transactions in Ethereum, but there are some differences in how they work and the data they return.

In Ethereum, when you perform a trace call to analyze internal transactions (using the debug_traceTransaction RPC method), you can see the sequence of calls and interactions that occur within a transaction, including contract calls and their effects.

In Solana, cross-program invocations refer to calling other Solana programs (smart contracts) from within a Solana program. Solana programs are usually written in Rust and compiled to a custom bytecode called BPF (Berkeley Packet Filter) bytecode, which is executed on Solana's blockchain. When a Solana program invokes another Solana program, it's a cross-program invocation.

However, the information and data you can retrieve from cross-program invocations in Solana are more focused on the interactions between Solana programs and may not include the same level of detail as Ethereum's internal transactions. Solana's native transaction structure and programming model are designed for high performance and parallelism, which may not directly align with Ethereum's EVM-based smart contract interactions.

If you're looking for a direct equivalent of Ethereum's internal transaction tracing in Solana, it's important to note that Solana's architecture and design might not provide the exact same granularity of information. Solana's focus is more on achieving high throughput and efficiency, while Ethereum's focus is on providing a comprehensive view of contract interactions.

To explore cross-program invocations in Solana, you can refer to the Solana documentation for information on how to use the invoke method within Solana programs, as well as the available RPC methods to interact with Solana programs and retrieve information about their execution.


To learn more about building with Solana, visit the following resources:


About QuickNode

QuickNode is building infrastructure to support the future of Web3. Since 2017, we've worked with hundreds of developers and companies, helping scale dApps and providing high-performance access to 24+ blockchains. Subscribe to our newsletter for more content like this, and stay in the loop with what's happening in Web3!‌