Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: rpc endpoints #481

Merged
merged 25 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e999670
refactor: remove backend test
Sep 2, 2024
9864cbe
fix: remove duplicate layout element due to past conflict
Sep 2, 2024
f190ea8
refactor: remove backend commands
Sep 2, 2024
2ec0094
Merge branch 'staging' into 432-refactor-rpc-endponts
Sep 3, 2024
589ec48
refactor: rename 'get_contract_state' endpoint to 'call'
Sep 3, 2024
2989e3c
refactor: remove 'send_transaction' endpoint
Sep 3, 2024
89e1a37
Revert "refactor: remove backend commands"
Sep 4, 2024
6bedf57
Revert "refactor: remove backend test"
Sep 4, 2024
fcbd366
Merge remote-tracking branch 'origin/staging' into 432-refactor-rpc-e…
Sep 4, 2024
9d3df7f
refactor: remove create_account and debug_simulator
Sep 4, 2024
c4e6330
Merge branch 'staging' into 432-refactor-rpc-endponts
Sep 5, 2024
6793f12
Revert "fix: remove duplicate layout element due to past conflict"
Sep 6, 2024
5f50fd6
Merge branch 'staging' into 432-refactor-rpc-endponts
Sep 6, 2024
1986e8f
Merge branch 'staging' into 432-refactor-rpc-endponts
Sep 6, 2024
f7e9027
refactor: adapt call endpoint with standard eth params
Sep 9, 2024
f992b73
test: update json rpc unit test
Sep 9, 2024
90cd7b7
refactor: change order of address params
Sep 9, 2024
9f3b822
refactor: remove unused rpc method
Sep 9, 2024
4505e11
fix: Merge branch 'staging' into 432-refactor-rpc-endponts
Sep 10, 2024
e506468
test: update integration tests to match new call endpoint
Sep 10, 2024
59a2ac2
refactor: rename method to normalize spec, fix typo
Sep 10, 2024
942121f
refactor: rename call_contract_method to send_transaction
Sep 10, 2024
9c5488a
test: refactor call method in integration tests
Sep 10, 2024
2bb985d
refactor: simplify imports
Sep 11, 2024
a766157
Merge branch 'main' into 432-refactor-rpc-endponts
Sep 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions backend/protocol_rpc/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,17 +253,26 @@ def get_transaction_by_id(
return transactions_processor.get_transaction_by_id(transaction_id)


def get_contract_state(
def call(
accounts_manager: AccountsManager,
msg_handler: MessageHandler,
contract_address: str,
method_name: str,
method_args: list,
to_address: str,
from_address: str = "",
input: str = "",
# Future parameters:
# gas: int = 0,
# gas_price: int = 0,
# value: int = 0,
) -> dict:
if not accounts_manager.is_valid_address(contract_address):
raise InvalidAddressError(contract_address)
if not accounts_manager.is_valid_address(from_address):
raise InvalidAddressError(from_address)

contract_account = accounts_manager.get_account_or_fail(contract_address)
if not accounts_manager.is_valid_address(to_address):
raise InvalidAddressError(to_address)

decoded_data = decode_method_call_data(input)

contract_account = accounts_manager.get_account_or_fail(to_address)
node = Node(
contract_snapshot=None,
address="",
Expand All @@ -275,10 +284,18 @@ def get_contract_state(
leader_receipt=None,
msg_handler=msg_handler,
)

method_args = decoded_data.function_args
if isinstance(method_args, str):
try:
method_args = json.loads(method_args)
except json.JSONDecodeError:
method_args = [method_args]

return node.get_contract_data(
code=contract_account["data"]["code"],
state=contract_account["data"]["state"],
method_name=method_name,
method_name=decoded_data.function_name,
method_args=method_args,
)

Expand Down Expand Up @@ -406,7 +423,7 @@ def register_all_rpc_endpoints(
register_rpc_endpoint_for_partial(get_validator, validators_registry)

register_rpc_endpoint_for_partial(get_transaction_by_id, transactions_processor)
register_rpc_endpoint_for_partial(get_contract_state, accounts_manager, msg_handler)
register_rpc_endpoint_for_partial(call, accounts_manager, msg_handler)
register_rpc_endpoint_for_partial(
send_raw_transaction, transactions_processor, accounts_manager
)
8 changes: 6 additions & 2 deletions frontend/src/hooks/useContractQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,14 @@ export function useContractQueries() {

async function callReadMethod(method: string, methodArguments: string[]) {
try {
const methodParamsAsString = JSON.stringify(methodArguments);
const data = [method, methodParamsAsString];
const encodedData = wallet.encodeTransactionData(data);

const result = await rpcClient.getContractState({
contractAddress: address.value || '',
method,
methodArguments,
userAccount: accountsStore.currentUserAddress,
data: encodedData,
});

if (result?.status === 'error') {
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/services/IJsonRpcService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import type {
JsonRPCResponse,
JsonRpcResult,
GetContractStateResult,
CallContractFunctionResult,
CallContractFunctionRequest,
DeployContractRequest,
GetDeployedContractSchemaRequest,
CreateValidatorRequest,
Expand All @@ -19,9 +17,6 @@ export interface IJsonRpcService {
getContractState(
request: GetContractStateRequest,
): Promise<JsonRpcResult<GetContractStateResult>>;
callContractFunction(
request: CallContractFunctionRequest,
): Promise<JsonRpcResult<CallContractFunctionResult>>;
sendTransaction(singedTransaction: string): Promise<JsonRpcResult<any>>;
deployContract(request: DeployContractRequest): Promise<JsonRpcResult<any>>;
getContractSchema(
Expand Down
38 changes: 6 additions & 32 deletions frontend/src/services/JsonRpcService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import type {
JsonRPCResponse,
GetContractStateRequest,
GetContractStateResult,
CallContractFunctionRequest,
CallContractFunctionResult,
DeployContractRequest,
GetContractSchemaRequest,
GetDeployedContractSchemaRequest,
Expand All @@ -22,43 +20,19 @@ export class JsonRpcService implements IJsonRpcService {
* Retrieves the state of a contract at a specific address and method.
*
* @param {GetContractStateRequest} params - The parameters for the function.
* @param {string} params.userAccount - The user account calling the function.
* @param {string} params.contractAddress - The address of the contract.
* @param {string} params.method - The method of the contract.
* @param {any[]} params.methodArguments - The arguments for the method.
* @param {string} params.data - The encoded data including function name and arguments.
* @return {Promise<JsonRpcResult<GetContractStateResult>>} A promise that resolves to the result of the contract state retrieval.
*/
async getContractState({
contractAddress,
method,
methodArguments,
userAccount,
data,
}: GetContractStateRequest): Promise<JsonRpcResult<GetContractStateResult>> {
const { result } = await this.rpcClient.call<GetContractStateResult>({
method: 'get_contract_state',
params: [contractAddress, method, methodArguments],
});
return result;
}
/**
* Calls a contract function and returns the result.
*
* @param {CallContractFunctionResult} params - The parameters for the function.
* @param {string} params.userAccount - The user account calling the function.
* @param {string} params.contractAddress - The address of the contract.
* @param {string} params.method - The method of the contract.
* @param {any[]} params.params - The parameters for the method.
* @return {Promise<JsonRpcResult<any>>} A promise that resolves to the result of the contract function call.
*/
async callContractFunction({
userAccount,
contractAddress,
method,
params,
}: CallContractFunctionRequest): Promise<
JsonRpcResult<CallContractFunctionResult>
> {
const { result } = await this.rpcClient.call<CallContractFunctionResult>({
method: 'call_contract_function',
params: [userAccount, contractAddress, method, params],
method: 'call',
params: [contractAddress, userAccount, data],
});
return result;
}
Expand Down
9 changes: 1 addition & 8 deletions frontend/src/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,8 @@ export interface JsonRPCRequest {

export interface GetContractStateRequest {
contractAddress: string;
method: string;
methodArguments: any[];
}

export interface CallContractFunctionRequest {
userAccount: string;
contractAddress: string;
method: string;
params: any[];
data: string;
}

export interface DeployContractRequest {
Expand Down
31 changes: 0 additions & 31 deletions frontend/src/types/results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,3 @@ export interface JsonRpcResult<T> {
}

export interface GetContractStateResult extends Record<string, any> {}

export interface CallContractFunctionResult {
execution_output: {
consensus_data: string;
leader_data: {
result: {
args: any[];
class: string;
contract_state: string;
eq_outputs: {
leader: Record<number, string>;
};
gas_used: number;
method: string;
mode: string;
node_config: {
address: string;
config: any;
id: number;
model: string;
provider: string;
stake: number;
type: string;
updated_at: string;
};
};

vote: string;
};
};
}
90 changes: 5 additions & 85 deletions frontend/test/unit/services/JsonRpcService.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { JsonRpcService } from '@/services/JsonRpcService';
import type { IJsonRpcService } from '@/services/IJsonRpcService';
import type { IRpcClient } from '@/clients/rpc';
import type {
CallContractFunctionResult,
GetContractStateResult,
JsonRPCResponse,
} from '@/types';
import type { GetContractStateResult, JsonRPCResponse } from '@/types';
import { describe, expect, it, vi, afterEach, beforeEach } from 'vitest';

describe('JsonRprService', () => {
Expand Down Expand Up @@ -34,8 +30,8 @@ describe('JsonRprService', () => {
};
const input = {
contractAddress: '0x58FaA28cbAA1b52F8Ec8D3c6FFCE6f1AaF8bEEB1',
method: 'get_have_coin',
methodArguments: [],
userAccount: '0xFEaedeC4c6549236EaF49C1F7c5cf860FD2C3fcB',
data: '0x',
};
it('should call rpc client', async () => {
const spy = vi
Expand All @@ -46,8 +42,8 @@ describe('JsonRprService', () => {
expect(spy.getMockName()).toEqual('call');
expect(rpcClient.call).toHaveBeenCalledTimes(1);
expect(rpcClient.call).toHaveBeenCalledWith({
method: 'get_contract_state',
params: [input.contractAddress, input.method, []],
method: 'call',
params: [input.contractAddress, input.userAccount, input.data],
});
});

Expand All @@ -59,80 +55,4 @@ describe('JsonRprService', () => {
expect(result).to.deep.equal(mockResponse.result);
});
});

describe('callContractFunction', () => {
const mockResponse: JsonRPCResponse<CallContractFunctionResult> = {
id: 'test',
jsonrpc: '2.0',
result: {
data: {
execution_output: {
consensus_data:
'{"final":false,"votes":{"0x46CFb7C09EEc0661dfaE35edf9eefb516Be8c9ED":"agree","0x5cceEce5b0DD5Ca98899CC11cAbd2BcbeeAdaBB2":"agree","0xbF2Ea1ac0FC66dbD4C6b0C78B646571c02e0245c":"agree","0xA9e410DcF02ddBdC525DADebEDC865d119479AB8":"agree"},"leader":{"vote":"agree","result":{"args":["test"],"class":"WizardOfCoin","contract_state":"gASVNwAAAAAAAACMCF9fbWFpbl9flIwMV2l6YXJkT2ZDb2lulJOUKYGUfZSMCWhhdmVfY29pbpSMBFRydWWUc2Iu","eq_outputs":{"leader":{"0":"{\\n\\"reasoning\\": \\"I cannot give you the coin because it holds powerful magic that must not fall into the wrong hands.\\",\\n\\"give_coin\\": False\\n}"}},"gas_used":0,"method":"ask_for_coin","mode":"leader","node_config":{"address":"0xB1971EAfB15Fd2a04afAd55A7d5eF9940c0dd464","config":{},"id":1,"model":"gpt-4o-mini","provider":"openai","stake":6.9066502309882605,"type":"validator","updated_at":"05/27/2024, 19:50:36"}}},"validators":[{"vote":"agree","result":{"args":["test"],"class":"WizardOfCoin","contract_state":"gASVNwAAAAAAAACMCF9fbWFpbl9flIwMV2l6YXJkT2ZDb2lulJOUKYGUfZSMCWhhdmVfY29pbpSMBFRydWWUc2Iu","eq_outputs":{"leader":{"0":"{\\n\\"reasoning\\": \\"I cannot give you the coin because it holds powerful magic that must not fall into the wrong hands.\\",\\n\\"give_coin\\": False\\n}"}},"gas_used":0,"method":"ask_for_coin","mode":"validator","node_config":{"address":"0x5cceEce5b0DD5Ca98899CC11cAbd2BcbeeAdaBB2","config":{},"id":10,"model":"gpt-4","provider":"openai","stake":8.800945043259368,"type":"validator","updated_at":"05/27/2024, 19:50:36"}}},{"vote":"agree","result":{"args":["test"],"class":"WizardOfCoin","contract_state":"gASVNwAAAAAAAACMCF9fbWFpbl9flIwMV2l6YXJkT2ZDb2lulJOUKYGUfZSMCWhhdmVfY29pbpSMBFRydWWUc2Iu","eq_outputs":{"leader":{"0":"{\\n\\"reasoning\\": \\"I cannot give you the coin because it holds powerful magic that must not fall into the wrong hands.\\",\\n\\"give_coin\\": False\\n}"}},"gas_used":0,"method":"ask_for_coin","mode":"validator","node_config":{"address":"0xbF2Ea1ac0FC66dbD4C6b0C78B646571c02e0245c","config":{"mirostat":0,"mirostat_tau":6.0,"num_gqa":5,"num_thread":14,"repeat_penalty":1.3,"stop":"","tfs_z":1.0,"top_k":31,"top_p":0.96},"id":3,"model":"llama2","provider":"ollama","stake":1.7517946864812701,"type":"validator","updated_at":"05/27/2024, 19:50:36"}}},{"vote":"agree","result":{"args":["test"],"class":"WizardOfCoin","contract_state":"gASVNwAAAAAAAACMCF9fbWFpbl9flIwMV2l6YXJkT2ZDb2lulJOUKYGUfZSMCWhhdmVfY29pbpSMBFRydWWUc2Iu","eq_outputs":{"leader":{"0":"{\\n\\"reasoning\\": \\"I cannot give you the coin because it holds powerful magic that must not fall into the wrong hands.\\",\\n\\"give_coin\\": False\\n}"}},"gas_used":0,"method":"ask_for_coin","mode":"validator","node_config":{"address":"0xA9e410DcF02ddBdC525DADebEDC865d119479AB8","config":{"mirostat":0,"num_gpu":13,"repeat_penalty":1.8,"stop":"","temprature":0.1,"tfs_z":1.0},"id":2,"model":"llama2","provider":"ollama","stake":4.99630617747627,"type":"validator","updated_at":"05/27/2024, 19:50:36"}}}]}',
leader_data: {
result: {
args: ['test'],
class: 'WizardOfCoin',
contract_state:
'gASVNwAAAAAAAACMCF9fbWFpbl9flIwMV2l6YXJkT2ZDb2lulJOUKYGUfZSMCWhhdmVfY29pbpSMBFRydWWUc2Iu',
eq_outputs: {
leader: {
'0': '{\n"reasoning": "I cannot give you the coin because it holds powerful magic that must not fall into the wrong hands.",\n"give_coin": False\n}',
},
},
gas_used: 0,
method: 'ask_for_coin',
mode: 'leader',
node_config: {
address: '0xB1971EAfB15Fd2a04afAd55A7d5eF9940c0dd464',
config: {},
id: 1,
model: 'gpt-4o-mini',
provider: 'openai',
stake: 6.9066502309882605,
type: 'validator',
updated_at: '05/27/2024, 19:50:36',
},
},
vote: 'agree',
},
},
},
message: '',
status: 'success',
},
};
const input = {
userAccount: '0xFEaedeC4c6549236EaF49C1F7c5cf860FD2C3fcB',
contractAddress: '0x58FaA28cbAA1b52F8Ec8D3c6FFCE6f1AaF8bEEB1',
method: 'WizardOfCoin.ask_for_coin',
params: ['Give me the coin'],
};
it('should call rpc client', async () => {
const spy = vi
.spyOn(rpcClient, 'call')
.mockImplementationOnce(() => Promise.resolve(mockResponse));

await jsonRpcService.callContractFunction(input);
expect(spy.getMockName()).toEqual('call');
expect(rpcClient.call).toHaveBeenCalledTimes(1);
expect(rpcClient.call).toHaveBeenCalledWith({
method: 'call_contract_function',
params: [
input.userAccount,
input.contractAddress,
input.method,
[...input.params],
],
});
});

it('should call contract function and return result', async () => {
vi.spyOn(rpcClient, 'call').mockImplementationOnce(() =>
Promise.resolve(mockResponse),
);
const result = await jsonRpcService.callContractFunction(input);
expect(result).to.equal(mockResponse.result);
});
});
});
21 changes: 16 additions & 5 deletions tests/common/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from dotenv import load_dotenv
from eth_account import Account

from tests.common.transactions import construct_signed_transaction
from tests.common.transactions import sign_transaction, encode_transaction_data

load_dotenv()

Expand Down Expand Up @@ -44,6 +44,19 @@ def get_transaction_by_id(transaction_id: str):


def call_contract_method(
contract_address: str,
from_account: Account,
method_name: str,
method_args: list,
):
params_as_string = json.dumps(method_args)
encoded_data = encode_transaction_data([method_name, params_as_string])
return post_request_localhost(
payload("call", contract_address, from_account.address, encoded_data)
).json()


def send_transaction(
account: Account,
contract_address: str,
method_name: str,
Expand All @@ -55,17 +68,15 @@ def call_contract_method(
if method_name is None and method_args is None
else [method_name, json.dumps(method_args)]
)
signed_transaction = construct_signed_transaction(
account, call_data, contract_address, value
)
signed_transaction = sign_transaction(account, call_data, contract_address, value)
return send_raw_transaction(signed_transaction)


def deploy_intelligent_contract(
account: Account, contract_code: str, constructor_params: str
):
deploy_data = [contract_code, constructor_params]
signed_transaction = construct_signed_transaction(account, deploy_data)
signed_transaction = sign_transaction(account, deploy_data)
return send_raw_transaction(signed_transaction)


Expand Down
6 changes: 3 additions & 3 deletions tests/common/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def encode_transaction_data(data: list) -> str:
return to_hex(serialized_data)


def construct_signed_transaction(
def sign_transaction(
account: Account, data: list = None, to: str = None, value: int = 0
) -> dict:
transaction = {
Expand All @@ -22,8 +22,8 @@ def construct_signed_transaction(
}

if data is not None:
enconded_data = encode_transaction_data(data)
transaction["data"] = enconded_data
encoded_data = encode_transaction_data(data)
transaction["data"] = encoded_data

signed_transaction = Account.sign_transaction(transaction, account.key)
return to_hex(signed_transaction.raw_transaction)
Loading
Loading