NOTICE: Development has moved to: https://github.com/tablelandnetwork/tableland-js/
A lightweight development environment for Tableland databases.
Local Tableland provides developers with a user-friendly, lightweight development environment for working with Tableland, making it easier to build, test, and deploy decentralized database applications in web3. You can create a local sandbox environment to build or set up automated tests against a Local Tableland network.
A Tableland network is fundamentally made up of two parts:
- An EVM compatible blockchain with the Tableland Registry contract deployed to it.
- A Tableland Validator node that can listen to events emitted from the contract and materialize tables.
This package contains tooling to get a sandboxed single Tableland network node running locally aside a Hardhat blockchain node. It is useful for local-only development, removing the need for testnet faucets and giving you easy access to logs of everything that is happening on the Tableland network during development.
From your project, install the @tableland/local
package as a development dependency:
npm install --save-dev @tableland/local
To spin up a Local Tableland network, you can run the following:
npx local-tableland
Under the hood, this will run a binary release of the Tableland Validator, using the Tableland Registry smart contract as an npm
dependency and deploying it to the local Hardhat blockchain node. To shut down Local Tableland, simply quit / exit your terminal session.
You will see logs from both the Registry contract and the Validator, which are prefixed with the origin of the log. If you want to silence the logs, you can use the silent
flag; verbose logs (default) use the verbose
flag:
npx local-tableland --silent
npx local-tableland --verbose
Under the hood, Local Tableland is running an in-memory instance of a Hardhat network. To connect and interact with the Hardhat network (e.g., using a browser wallet), you'll need to use the following:
- RPC URL:
http://127.0.0.1:8545
- Chain ID:
31337
Checkout the Hardhat docs for more details.
Separately, a Local Tableland node is running. You can interact with the node at the following base URL, such as using any of the Gateway APIs or CLI tool:
- Base URL:
http://localhost:8080
Using the JavaScript SDK with a Local Tableland sandboxed network is the same as any network; you simply configure the provider
with the chain you want to use. For example, you can pass the local URL http://127.0.0.1:8545
to getDefaultProvider
from ethers
and connect the signer
to it:
import { Database } from "@tableland/sdk";
import { getDefaultProvider, Wallet } from "ethers";
// The local EVM node uses the default Hardhat standalone node URL.
const localProviderUrl = "http://127.0.0.1:8545";
// Hardhat-provided private key (the second account).
const privateKey =
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
const wallet = new Wallet(privateKey);
const provider = getDefaultProvider(localProviderUrl);
const signer = wallet.connect(provider);
const db = new Database({ signer });
When you spin up a Hardhat node, it comes with a setup of default accounts preloaded with 10,000 local-only ETH (symbol: GO
); these accounts have totally open private keys. The example above (0x59c6...
) is the second account since the first one is already used by Local Tableland when deploying the Registry contract.
If you are using Local Tableland to run tests for your project, or want to start a sandbox network programmatically for any reason, there are some helper utilities exported by this package to help reduce boilerplate. For example, the following demonstrates how to start a local node by:
- Instantiating
LocalTableland
. - Starting the network up and making sure it's ready.
- Retrieving connected account (from Hardhat).
- Getting / connecting a
Database
. - Getting a
Registry
orValidator
instance (via the SDK). - Stopping the network.
import {
LocalTableland,
getDatabase,
getRegistry,
getValidator,
getAccounts,
} from "@tableland/local";
// Create an instance of Local Tableland.
const lt = new LocalTableland({
// Silent or verbose can be set via an options object as the first arg.
silent: true,
});
const go = async function () {
// Start up Local Tableland and make sure it's ready to be interacted with.
lt.start();
await lt.isReady();
// Get wallets aka signers for all 25 of the public Hardhat accounts.
const accounts = getAccounts();
// Get a Database instance that's connected to the passed in account.
const db = getDatabase(accounts[1]);
const response = await db
.prepare(`CREATE TABLE my_table (id integer primary key, name text);`)
.all();
console.log(response);
// Get an instance of the Registry class; more details here:
// https://docs.tableland.xyz/sdk/core/registry-api
const registry = getRegistry(accounts[1]);
// List the tables owned by `accounts[1]`
const myTables = await registry.listTables();
console.log(myTables);
// Get an instance of the Validator class; more details here:
// https://docs.tableland.xyz/sdk/core/validator-api
const validator = getValidator(db.config.baseUrl);
const tableData = await validator.getTableById({
chainId: 31337,
tableId: "1",
});
console.log(tableData);
// Stop Local Tableland.
await stop();
};
// Code to shutdown the Tableland and Hardhat nodes.
const stop = async function () {
await lt.shutdown();
};
// Catch errors and log them.
go().catch((err) => console.log(err));
A best practice for testing is to start a single local network and run all of your tests against it, i.e., don't create an instance for each test. Doing this will speed up test execution significantly!
Using Local Tableland to test a Hardhat project is straightforward. The one key point is that instead of letting Hardhat automatically start a node for you, you will be letting Local Tableland start the node. To do this, you simply have to include the network flag with the value localhost
. For example, instead of running npx hardhat test
, you should run:
npx hardhat --network localhost test
Consider the basic example below that assumes you have created MyContract
in your Hardhat project and are deploying it. It imports mocha
for writing the tests along with some of the helper methods outlined in the previous example.
import { after, before, describe, test } from "mocha";
import { LocalTableland, getAccounts } from "@tableland/local";
// Create an instance of Local Tableland.
const lt = new LocalTableland({
// Silent or verbose can be set via an options object as the first arg.
silent: true,
});
const accounts = getAccounts();
// Create and start a single instance (i.e., not for each test).
before(async function () {
this.timeout(25000);
lt.start();
await lt.isReady();
// Deploy a contract to the Local Tableland network.
// This contract might create tables do inserts, etc...
const Factory = await ethers.getContractFactory(
"MyContract"
);
const myContractInstance = (await Factory.deploy()) as MyContract;
await myContractInstance.deployed();
// If your Contract creates tables, you will have to optimistically wait to allow
// the Validator to materialize them.
// NOTE: to determine if a table has been materialized using the transaction hash,
// you can use the REST API endpoint: `/api/v1/receipt/{chainId}/{transactionHash}`
await new Promise(resolve => setTimeout(() => resolve(), 5000));
});
after(async function () {
await lt.shutdown();
});
describe("Test MyContract and other apps", function () {
test("Should work end to end", async function () {
// Test an end to end example
});
// More tests...
})
See here for a full working example of testing a Hardhat project.
For those contributing to the core Tableland protocol, a common pattern is to spin up Local Tableland to interact with any changes you make to the core software during development: evm-tableland and go-tableland. This helps you check if your local changes are working across the entire network.
Set up a workspace that has all three of these repos cloned into it.
git clone https://github.com/tablelandnetwork/evm-tableland
git clone https://github.com/tablelandnetwork/go-tableland
git clone https://github.com/tablelandnetwork/local-tableland
Your workspace should have the following directories:
.
├── evm-tableland
├── go-tableland
└── local-tableland
Then, you will want to cd
into local-tableland
and create a tableland.config.js
file by running:
npx local-tableland --init
This will create tableland.config.js
in the root of the local-tableland
directory; it defines the relative paths to the aforementioned core protocol directories:
module.exports = {
validatorDir: "../go-tableland",
registryDir: "../evm-tableland",
verbose: false,
silent: false,
};
Once this is all set up, you can check if any changes you make to go-tableland
and/or evm-tableland
code cause things to break. Do this by running the following command from within the local-tableland
directory:
npm test
Keep an eye out for zombie processes. Killing the Local Tableland process should kill all of the sub-processes, and the cleanup of everything (including Docker, if relevant) is done during startup. But, it's still worth keeping an eye out—and if you find any problems or ways for this tooling to better manage cleanups, please open an issue!
PRs accepted.
Small note: If editing the README, please conform to the standard-readme specification.
MIT AND Apache-2.0, © 2021-2022 Tableland Network Contributors