Skip to content

feat: nc args new serialization #845

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

Merged
merged 21 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3180873
feat: nc args new serialization
r4mmer Apr 24, 2025
f0bffd4
Merge remote-tracking branch 'origin/master' into feat/nc-args-serial…
r4mmer May 6, 2025
0716c81
Merge remote-tracking branch 'origin/master' into feat/nc-args-serial…
r4mmer May 6, 2025
779c017
feat: deserialize container types
r4mmer May 6, 2025
25a8e28
tests: new args serialization tests
r4mmer May 6, 2025
97cfc56
tests: add Signed VarInt case
r4mmer May 7, 2025
2798ed1
feat: encoding large strings and buffers should use unsigned leb128
r4mmer May 7, 2025
c6afbb8
chore: add unsigned leb128 util changes
r4mmer May 7, 2025
6098b73
Merge remote-tracking branch 'origin/master' into feat/nc-args-serial…
r4mmer May 7, 2025
ed9457f
chore: linter changes
r4mmer May 7, 2025
c57d0c2
feat: removed length of individual args from serialize nano header
r4mmer May 9, 2025
4acc04d
feat: deserialize args into single buffer before parsing
r4mmer May 9, 2025
39c5dd4
chore: change fullnode image for integration testing
r4mmer May 9, 2025
c5cf8a4
feat: address and args serialization issues
r4mmer May 10, 2025
327d6ce
Merge remote-tracking branch 'origin/master' into feat/nc-args-serial…
r4mmer May 12, 2025
9f098b0
chore: review changes
r4mmer May 16, 2025
43a6fc1
chore: review changes
r4mmer May 16, 2025
1819447
chore: review changes
r4mmer May 16, 2025
7973b49
Merge remote-tracking branch 'origin/master' into feat/nc-args-serial…
r4mmer May 16, 2025
ec0c2f4
feat: new SignedData and method argument parser (#862)
r4mmer May 19, 2025
425d51d
chore: remove branches from CI
r4mmer May 19, 2025
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
3 changes: 2 additions & 1 deletion __tests__/integration/configuration/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:

fullnode:
image:
${HATHOR_LIB_INTEGRATION_TESTS_FULLNODE_IMAGE:-hathornetwork/hathor-core:experimental-nano-preview-20250428}
${HATHOR_LIB_INTEGRATION_TESTS_FULLNODE_IMAGE:-hathornetwork/hathor-core:experimental-nano-preview-20250506}
command: [
"run_node",
"--listen", "tcp:40404",
Expand All @@ -17,6 +17,7 @@ services:
"--unsafe-mode", "nano-testnet-alpha",
"--data", "./tmp",
"--nc-indices",
"--nc-exec-logs", "all",
]
environment:
HATHOR_CONFIG_YAML: privnet/conf/privnet.yml
Expand Down
115 changes: 76 additions & 39 deletions __tests__/integration/nanocontracts/bet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from '../../../src/errors';
import { OutputType } from '../../../src/wallet/types';
import NanoContractTransactionParser from '../../../src/nano_contracts/parser';
import { NanoContractSignedData } from '../../../src/nano_contracts/types';

let fundsTx;
const builtInBlueprintId = '3cb032600bdf7db784800e4ea911b10676fa2f67591f82bb62628c234e771595';
Expand All @@ -43,7 +44,7 @@ describe('full cycle of bet nano contract', () => {
let mhWallet;

beforeAll(async () => {
hWallet = await generateWalletHelper();
hWallet = await generateWalletHelper(null);
fundsTx = await GenesisWalletHelper.injectFunds(
hWallet,
await hWallet.getAddressAtIndex(0),
Expand Down Expand Up @@ -72,7 +73,7 @@ describe('full cycle of bet nano contract', () => {
expect(txId).toBeDefined();
await waitForTxReceived(wallet, txId);
// We need to wait for the tx to get a first block, so we guarantee it was executed
await waitTxConfirmed(wallet, txId);
await waitTxConfirmed(wallet, txId, null);
// Now we query the transaction from the full node to double check it's still valid after the nano execution
// and it already has a first block, so it was really executed
const txAfterExecution = await wallet.getFullTxById(txId);
Expand Down Expand Up @@ -121,14 +122,28 @@ describe('full cycle of bet nano contract', () => {
network,
tx1Data.tx.nc_args
);
tx1Parser.parseAddress(network);
tx1Parser.parseAddress();
await tx1Parser.parseArguments();
expect(tx1Parser.address.base58).toBe(address0);
expect(tx1Parser.parsedArgs).toStrictEqual([
{ name: 'oracle_script', type: 'TxOutputScript', parsed: oracleData },
{ name: 'token_uid', type: 'TokenUid', parsed: Buffer.from([NATIVE_TOKEN_UID]) },
{ name: 'date_last_bet', type: 'Timestamp', parsed: dateLastBet },
]);
expect(tx1Parser.address?.base58).toBe(address0);
expect(tx1Parser.parsedArgs).not.toBeNull();
if (tx1Parser.parsedArgs === null) {
throw new Error('Could not parse args');
}
expect(tx1Parser.parsedArgs).toHaveLength(3);
expect(tx1Parser.parsedArgs[0]).toMatchObject({
name: 'oracle_script',
type: 'TxOutputScript',
});
// @ts-expect-error toMatchBuffer is defined in setupTests.js
expect(tx1Parser.parsedArgs[0].value).toMatchBuffer(oracleData);
expect(tx1Parser.parsedArgs[1]).toMatchObject({ name: 'token_uid', type: 'TokenUid' });
// @ts-expect-error toMatchBuffer is defined in setupTests.js
expect(tx1Parser.parsedArgs[1].value).toMatchBuffer(Buffer.from(NATIVE_TOKEN_UID, 'hex'));
expect(tx1Parser.parsedArgs[2]).toMatchObject({
name: 'date_last_bet',
type: 'Timestamp',
value: dateLastBet,
});

// First validate some bet arguments error handling
const address2 = await wallet.getAddressAtIndex(2);
Expand Down Expand Up @@ -197,13 +212,20 @@ describe('full cycle of bet nano contract', () => {
network,
txBetData.tx.nc_args
);
txBetParser.parseAddress(network);
txBetParser.parseAddress();
await txBetParser.parseArguments();
expect(txBetParser.address.base58).toBe(address2);
expect(txBetParser.parsedArgs).toStrictEqual([
{ name: 'address', type: 'Address', parsed: address2 },
{ name: 'score', type: 'str', parsed: '1x0' },
]);
expect(txBetParser.address?.base58).toBe(address2);
expect(txBetParser.parsedArgs).not.toBeNull();
if (txBetParser.parsedArgs === null) {
throw new Error('Could not parse args');
}
expect(txBetParser.parsedArgs).toHaveLength(2);
expect(txBetParser.parsedArgs[0]).toMatchObject({
name: 'address',
type: 'Address',
value: address2,
});
expect(txBetParser.parsedArgs[1]).toMatchObject({ name: 'score', type: 'str', value: '1x0' });

const utxos2 = await wallet.getUtxos();
// We must have one utxo in the address 0 of 900 HTR
Expand Down Expand Up @@ -245,13 +267,20 @@ describe('full cycle of bet nano contract', () => {
network,
txBet2Data.tx.nc_args
);
txBet2Parser.parseAddress(network);
txBet2Parser.parseAddress();
await txBet2Parser.parseArguments();
expect(txBet2Parser.address.base58).toBe(address3);
expect(txBet2Parser.parsedArgs).toStrictEqual([
{ name: 'address', type: 'Address', parsed: address3 },
{ name: 'score', type: 'str', parsed: '2x0' },
]);
expect(txBet2Parser.address?.base58).toBe(address3);
expect(txBet2Parser.parsedArgs).not.toBeNull();
if (txBet2Parser.parsedArgs === null) {
throw new Error('Could not parse args');
}
expect(txBet2Parser.parsedArgs).toHaveLength(2);
expect(txBet2Parser.parsedArgs[0]).toMatchObject({
name: 'address',
type: 'Address',
value: address3,
});
expect(txBet2Parser.parsedArgs[1]).toMatchObject({ name: 'score', type: 'str', value: '2x0' });

// Get nc history
const txIds = [tx1.hash, txBet.hash, txBet2.hash];
Expand Down Expand Up @@ -296,10 +325,10 @@ describe('full cycle of bet nano contract', () => {
expect(ncState.fields[`withdrawals.a'${address3}'`].value).toBeUndefined();

// Set result to '1x0'
const nanoSerializer = new Serializer();
const nanoSerializer = new Serializer(network);
const result = '1x0';
const resultSerialized = nanoSerializer.serializeFromType(result, 'str');
const inputData = await getOracleInputData(oracleData, resultSerialized, wallet);
const inputData = await getOracleInputData(oracleData, tx1.hash, resultSerialized, wallet);
const txSetResult = await wallet.createAndSendNanoContractTransaction('set_result', address1, {
ncId: tx1.hash,
args: [`${bufferToHex(inputData)},${result},str`],
Expand All @@ -320,16 +349,24 @@ describe('full cycle of bet nano contract', () => {
network,
txSetResultData.tx.nc_args
);
txSetResultParser.parseAddress(network);
txSetResultParser.parseAddress();
await txSetResultParser.parseArguments();
expect(txSetResultParser.address.base58).toBe(address1);
expect(txSetResultParser.parsedArgs).toStrictEqual([
{
name: 'result',
type: 'SignedData[str]',
parsed: `${bufferToHex(inputData)},${result},str`,
},
]);
expect(txSetResultParser.address?.base58).toBe(address1);
expect(txSetResultParser.parsedArgs).not.toBeNull();
if (txSetResultParser.parsedArgs === null) {
throw new Error('Could not parse args');
}
expect(txSetResultParser.parsedArgs).toHaveLength(1);
expect(txSetResultParser.parsedArgs[0]).toMatchObject({
name: 'result',
type: 'SignedData[str]',
});
expect((txSetResultParser.parsedArgs[0].value as NanoContractSignedData).type).toEqual('str');
expect(
(txSetResultParser.parsedArgs[0].value as NanoContractSignedData).signature
// @ts-expect-error toMatchBuffer is defined in setupTests.js
).toMatchBuffer(inputData);
expect((txSetResultParser.parsedArgs[0].value as NanoContractSignedData).value).toEqual(result);

const withdrawalData = {
ncId: tx1.hash,
Expand Down Expand Up @@ -417,14 +454,14 @@ describe('full cycle of bet nano contract', () => {

const txWithdrawalParser = new NanoContractTransactionParser(
blueprintId,
'set_result',
'withdraw',
txWithdrawalData.tx.nc_pubkey,
network,
txWithdrawalData.tx.nc_args
);
txWithdrawalParser.parseAddress(network);
txWithdrawalParser.parseAddress();
await txWithdrawalParser.parseArguments();
expect(txWithdrawalParser.address.base58).toBe(address2);
expect(txWithdrawalParser.address?.base58).toBe(address2);
expect(txWithdrawalParser.parsedArgs).toBe(null);

// Get state again
Expand Down Expand Up @@ -552,7 +589,7 @@ describe('full cycle of bet nano contract', () => {

jest.spyOn(wallet.storage, 'processHistory');
expect(wallet.storage.processHistory.mock.calls.length).toBe(0);
await waitTxConfirmed(wallet, txWithdrawal2.hash);
await waitTxConfirmed(wallet, txWithdrawal2.hash, null);
const txWithdrawal2Data = await wallet.getFullTxById(txWithdrawal2.hash);

// The tx became voided after the block because of the nano execution
Expand Down Expand Up @@ -585,16 +622,16 @@ describe('full cycle of bet nano contract', () => {
// Add funds and validate address meta
await GenesisWalletHelper.injectFunds(ocbWallet, address0, 1000n);
const address0Meta = await ocbWallet.storage.store.getAddressMeta(address0);
expect(address0Meta.numTransactions).toBe(1);
expect(address0Meta?.numTransactions).toBe(1);

// Use the bet blueprint code
const code = fs.readFileSync('./__tests__/integration/configuration/bet.py', 'utf8');
const tx = await ocbWallet.createAndSendOnChainBlueprintTransaction(code, address10);
// Wait for the tx to be confirmed, so we can use the on chain blueprint
await waitTxConfirmed(ocbWallet, tx.hash);
await waitTxConfirmed(ocbWallet, tx.hash!, null);
// We must have one transaction in the address10 now
const newAddress10Meta = await ocbWallet.storage.store.getAddressMeta(address10);
expect(newAddress10Meta.numTransactions).toBe(1);
expect(newAddress10Meta?.numTransactions).toBe(1);
// Execute the bet blueprint tests
await executeTests(ocbWallet, tx.hash);
});
Expand Down
Loading
Loading