Skip to content

Commit

Permalink
ALL-2878 - Add Beacon methods to eth (#999)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hathoriel authored Oct 24, 2023
1 parent eb79ee3 commit 81b313d
Show file tree
Hide file tree
Showing 19 changed files with 453 additions and 14 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
env:
SDK_ERROR_MESSAGES_VERSION: "master"
V4_API_KEY_MAINNET: ${{ secrets.V4_API_KEY_MAINNET }}
V4_API_KEY_TESTNET: ${{ secrets.V4_API_KEY_TESTNET }}
V3_API_KEY_MAINNET: ${{ secrets.V3_API_KEY_MAINNET }}
V3_API_KEY_TESTNET: ${{ secrets.V3_API_KEY_TESTNET }}
NON_TATUM_RPC_ETH_URL: ${{ secrets.NON_TATUM_RPC_ETH_URL }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

env:
V4_API_KEY_MAINNET: ${{ secrets.V4_API_KEY_MAINNET }}
V4_API_KEY_TESTNET: ${{ secrets.V4_API_KEY_TESTNET }}
V3_API_KEY_MAINNET: ${{ secrets.V3_API_KEY_MAINNET }}
V3_API_KEY_TESTNET: ${{ secrets.V3_API_KEY_TESTNET }}
NON_TATUM_RPC_ETH_URL: ${{ secrets.NON_TATUM_RPC_ETH_URL }}
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [4.1.16] - 2023.10.24
### Added
- Added Beacon chain v1 support for the Ethereum

## [4.1.15] - 2023.10.24
### Added
- Added estimatefee rpc method to the Bitcoin Cash network
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tatumio/tatum",
"version": "4.1.15",
"version": "4.1.16",
"description": "Tatum JS SDK",
"author": "Tatum",
"repository": "https://github.com/tatumio/tatum-js",
Expand Down
5 changes: 5 additions & 0 deletions src/dto/GetI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface GetI {
path: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
basePath?: string
}
9 changes: 9 additions & 0 deletions src/dto/Network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ export const EVM_ARCHIVE_NON_ARCHIVE_LOAD_BALANCER_NETWORKS = [
Network.CHILIZ,
]

export const EVM_ARCHIVE_NON_ARCHIVE_BEACON_LOAD_BALANCER_NETWORKS = [
Network.ETHEREUM,
Network.ETHEREUM_SEPOLIA,
Network.ETHEREUM_HOLESKY,
]

export const TRON_NETWORKS = [Network.TRON, Network.TRON_SHASTA]
export const EOS_NETWORKS = [Network.EOS, Network.EOS_TESTNET]

Expand Down Expand Up @@ -291,6 +297,9 @@ export const isEvmLoadBalancerNetwork = (network: Network) => EVM_LOAD_BALANCER_
export const isEvmArchiveNonArchiveLoadBalancerNetwork = (network: Network) =>
EVM_ARCHIVE_NON_ARCHIVE_LOAD_BALANCER_NETWORKS.includes(network)

export const isEvmArchiveNonArchiveBeaconLoadBalancerNetwork = (network: Network) =>
EVM_ARCHIVE_NON_ARCHIVE_BEACON_LOAD_BALANCER_NETWORKS.includes(network)

export const isTronLoadBalancerNetwork = (network: Network) => TRON_LOAD_BALANCER_NETWORKS.includes(network)

export const isEosLoadBalancerNetwork = (network: Network) => EOS_LOAD_BALANCER_NETWORKS.includes(network)
Expand Down
79 changes: 79 additions & 0 deletions src/dto/rpc/EvmBasedRpcInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,82 @@ export interface EvmBasedRpcInterface {

txPoolInspect(): Promise<JsonRpcResponse<any>>
}

export interface EvmBeaconResponse<T> {
data: T
execution_optimistic?: boolean
finalized?: boolean
}

export type StateId = 'head' | 'genesis' | 'finalized' | 'justified' | string;
export type BlockId = 'head' | 'genesis' | 'finalized' | string;

export interface BlockQuery {
blockId: BlockId
}

export interface StateCommitteesQuery {
stateId: StateId
epoch?: string;
index?: string;
slot?: string;
}

export interface StateQuery {
stateId: StateId
}

export interface StateSyncCommitteesQuery extends StateQuery {
epoch?: string;
}

export interface ValidatorBalancesQuery extends StateQuery {
id?: string[]; // Array of either hex encoded public key or validator index
}

export type ValidatorStatus =
| 'pending_initialized'
| 'pending_queued'
| 'active_ongoing'
| 'active_exiting'
| 'active_slashed'
| 'exited_unslashed'
| 'exited_slashed'
| 'withdrawal_possible'
| 'withdrawal_done'
| 'active'
| 'pending'
| 'exited'
| 'withdrawal';

export interface ValidatorsQuery extends StateQuery {
id?: string[]; // Array of either hex encoded public key or validator index
status?: ValidatorStatus[];
}

export interface ValidatorQuery extends StateQuery {
validatorId: string; // Either a hex encoded public key or validator index
}


export interface EvmBeaconV1Interface {
getGenesis(): Promise<EvmBeaconResponse<any>>
getBlockHeaders(query?: { slot?: string, parentRoot?: string }): Promise<EvmBeaconResponse<any>>
getBlockHeader(query:BlockQuery): Promise<EvmBeaconResponse<any>>
getBlockRoot(query: BlockQuery): Promise<EvmBeaconResponse<any>>
getBlockAttestations(query: BlockQuery): Promise<EvmBeaconResponse<any>>
getStateCommittees(query: StateCommitteesQuery): Promise<EvmBeaconResponse<any>>
getStateFinalityCheckpoints(query: StateQuery): Promise<EvmBeaconResponse<any>>
getStateFork(query: StateQuery): Promise<EvmBeaconResponse<any>>
getStateRoot(query: StateQuery): Promise<EvmBeaconResponse<any>>
getStateSyncCommittees(query: StateSyncCommitteesQuery): Promise<EvmBeaconResponse<any>>
getStateValidatorBalances(query: ValidatorBalancesQuery): Promise<EvmBeaconResponse<any>>
getStateValidators(query: ValidatorsQuery): Promise<EvmBeaconResponse<any>>
getStateValidator(query: ValidatorQuery): Promise<EvmBeaconResponse<any>>
}

export interface EvmBasedBeaconRpcSuite extends EvmBasedRpcInterface, AbstractRpcInterface {
beacon: {
v1: EvmBeaconV1Interface
}
}
13 changes: 13 additions & 0 deletions src/e2e/ipfs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { EvmE2eUtils } from './rpc/evm/evm.e2e.utils';
import { Network } from '../dto';
import fs from 'fs';

describe.skip('IPFS', () => {
it('should upload file to IPFS', async () => {
const tatum = await EvmE2eUtils.initTatum(Network.ETHEREUM, process.env.V4_API_KEY_MAINNET);
const fileData = fs.readFileSync('./test.txt'); // Adjust the path to your file
const response = await tatum.ipfs.uploadFile({ file: fileData });
expect(response.status).toBe('SUCCESS');
expect(response.data.ipfsHash).toBeDefined();
});
});
71 changes: 71 additions & 0 deletions src/e2e/rpc/evm/eth/tatum.rpc.beacon.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Network } from '../../../../dto'
import { EvmE2eUtils } from '../evm.e2e.utils'
import { Ethereum } from '../../../../service'

describe('Beacon', () => {
describe('v1', () => {
const networks = [
Network.ETHEREUM_HOLESKY,
Network.ETHEREUM_SEPOLIA,
Network.ETHEREUM
];

describe.each(networks)('%s', (network) => {
it('should get genesis', async () => {
const tatum = await EvmE2eUtils.initTatum<Ethereum>(network, process.env.V4_API_KEY_TESTNET);
const { data } = await tatum.rpc.beacon.v1.getGenesis();
await tatum.destroy();
expect(data).toBeDefined();
});

it('should get state root', async () => {
const tatum = await EvmE2eUtils.initTatum<Ethereum>(network, process.env.V4_API_KEY_TESTNET);
const { data } = await tatum.rpc.beacon.v1.getStateRoot({ stateId: 'head' });
await tatum.destroy();
expect(data).toBeDefined();
});

it('should get block headers', async () => {
const tatum = await EvmE2eUtils.initTatum<Ethereum>(network, process.env.V4_API_KEY_TESTNET);
const { data } = await tatum.rpc.beacon.v1.getBlockHeaders({ slot: '1'});
await tatum.destroy();
expect(data).toBeDefined();
});

it('should get block root', async () => {
const tatum = await EvmE2eUtils.initTatum<Ethereum>(network, process.env.V4_API_KEY_TESTNET);
const { data } = await tatum.rpc.beacon.v1.getBlockRoot({ blockId: 'head' });
await tatum.destroy();
expect(data).toBeDefined();
});

it('should get state committees', async () => {
const tatum = await EvmE2eUtils.initTatum<Ethereum>(network, process.env.V4_API_KEY_TESTNET);
const { data } = await tatum.rpc.beacon.v1.getStateCommittees({ stateId: 'head' });
await tatum.destroy();
expect(data).toBeDefined();
});

it('should get state finality checkpoints', async () => {
const tatum = await EvmE2eUtils.initTatum<Ethereum>(network, process.env.V4_API_KEY_TESTNET);
const { data } = await tatum.rpc.beacon.v1.getStateFinalityCheckpoints({ stateId: 'head' });
await tatum.destroy();
expect(data).toBeDefined();
});

it('should get state fork', async () => {
const tatum = await EvmE2eUtils.initTatum<Ethereum>(network, process.env.V4_API_KEY_TESTNET);
const { data } = await tatum.rpc.beacon.v1.getStateFork({ stateId: 'head' });
await tatum.destroy();
expect(data).toBeDefined();
});

it('should get state sync committees', async () => {
const tatum = await EvmE2eUtils.initTatum<Ethereum>(network, process.env.V4_API_KEY_TESTNET);
const { data } = await tatum.rpc.beacon.v1.getStateSyncCommittees({ stateId: 'head' });
await tatum.destroy();
expect(data).toBeDefined();
});
})
})
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import process from 'process'
import { BaseEvm, Network, RpcNodeType, TatumSDK } from '../../../service'
import { EvmE2eUtils } from './evm.e2e.utils'
import { BaseEvm, Network, RpcNodeType, TatumSDK } from '../../../../service'
import { EvmE2eUtils } from '../evm.e2e.utils'

describe('Ethereum', () => {
it('should get token total supply', async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/e2e/rpc/rpc.e2e.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const RpcE2eUtils = {
retryDelay: 2000,
apiKey: {
v4: apiKey ?? process.env.V4_API_KEY_MAINNET,
},
}
}
},
}
86 changes: 86 additions & 0 deletions src/service/rpc/evm/AbstractBeaconV1EvmRpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Service } from 'typedi'
import {
BlockQuery,
EvmBeaconV1Interface,
EvmBeaconResponse,
StateCommitteesQuery,
StateQuery,
StateSyncCommitteesQuery, ValidatorBalancesQuery, ValidatorQuery, ValidatorsQuery,
} from '../../../dto'
import { GetI } from '../../../dto/GetI'
import { Constant, Utils } from '../../../util'

@Service()
export abstract class AbstractBeaconV1EvmRpc implements EvmBeaconV1Interface {
protected abstract get<T>(get: GetI): Promise<T>

getBlockAttestations({ blockId, ...rest }: BlockQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/blocks/${blockId}/attestations`, rest);
return this.get({ path });
}

getBlockHeader({ blockId, ...rest }: BlockQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/blocks/${blockId}/header`, rest);
return this.get({ path });
}
getBlockHeaders({ slot, parentRoot, ...rest }: { slot?: string; parentRoot?: string } = {}): Promise<EvmBeaconResponse<any>> {
const queryParams = {
...(slot ? { slot } : {}),
...(parentRoot ? { parentRoot } : {}),
...rest
};
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/headers`, queryParams);
return this.get({ path });
}

getBlockRoot({ blockId, ...rest }: BlockQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/blocks/${blockId}/root`, rest);
return this.get({ path });
}

getGenesis(): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/genesis`);
return this.get({ path });
}

getStateCommittees({ stateId, ...rest }: StateCommitteesQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/states/${stateId}/committees`, rest);
return this.get({ path });
}

getStateFinalityCheckpoints({ stateId, ...rest }: StateQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/states/${stateId}/finality_checkpoints`, rest);
return this.get({ path });
}

getStateFork({ stateId, ...rest }: StateQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/states/${stateId}/fork`, rest);
return this.get({ path });
}

getStateRoot({ stateId, ...rest }: StateQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/states/${stateId}/root`, rest);
return this.get({ path });
}

getStateSyncCommittees({ stateId, ...rest }: StateSyncCommitteesQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/states/${stateId}/sync_committees`, rest);
return this.get({ path });
}

getStateValidator({ stateId, validatorId, ...rest }: ValidatorQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/states/${stateId}/validators/${validatorId}`, rest);
return this.get({ path });
}

getStateValidatorBalances({ stateId, ...rest }: ValidatorBalancesQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/states/${stateId}/validator_balances`, rest);
return this.get({ path });
}

getStateValidators({ stateId, ...rest }: ValidatorsQuery): Promise<EvmBeaconResponse<any>> {
const path = Utils.addQueryParams(`${Constant.BEACON_PREFIX}/states/${stateId}/validators`, rest);
return this.get({ path });
}
}
29 changes: 29 additions & 0 deletions src/service/rpc/evm/BeaconV1EvmRpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Container, Service } from 'typedi'
import { LoadBalancer } from '../generic/LoadBalancer'
import { AbstractBeaconV1EvmRpc } from './AbstractBeaconV1EvmRpc'
import { GetI } from '../../../dto/GetI'

@Service({
factory: (data: { id: string }) => {
return new BeaconV1EvmRpc(data.id)
},
transient: true,
})
export class BeaconV1EvmRpc extends AbstractBeaconV1EvmRpc {
protected readonly loadBalancer: LoadBalancer

constructor(id: string) {
super()
this.loadBalancer = Container.of(id).get(LoadBalancer)
}

public destroy() {
this.loadBalancer.destroy()
}

protected get<T>(get: GetI): Promise<T> {
return this.loadBalancer.get(get)
}

}
24 changes: 24 additions & 0 deletions src/service/rpc/evm/EvmBeaconArchiveLoadBalancerRpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Container, Service } from 'typedi'
import { EvmBasedBeaconRpcSuite } from '../../../dto'
import { LoadBalancer } from '../generic/LoadBalancer'
import { EvmArchiveLoadBalancerRpc } from './EvmArchiveLoadBalancerRpc'
import { BeaconV1EvmRpc } from './BeaconV1EvmRpc'

@Service({
factory: (data: { id: string }) => {
return new EvmBeaconArchiveLoadBalancerRpc(data.id)
},
transient: true,
})
export class EvmBeaconArchiveLoadBalancerRpc extends EvmArchiveLoadBalancerRpc implements EvmBasedBeaconRpcSuite {
protected readonly loadBalancerRpc: LoadBalancer
public readonly beacon = {
v1: Container.of(this.id).get(BeaconV1EvmRpc),
};

constructor(private id: string) {
super(id);
this.loadBalancerRpc = Container.of(id).get(LoadBalancer);
}
}
Loading

0 comments on commit 81b313d

Please sign in to comment.