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

Tool 393 add relayer v 3 support #543

Merged
merged 6 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@multiversx/sdk-core",
"version": "13.15.0",
"version": "13.16.0",
"description": "MultiversX SDK for JavaScript and TypeScript",
"author": "MultiversX",
"homepage": "https://multiversx.com",
Expand Down
5 changes: 5 additions & 0 deletions src/converters/transactionsConverter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Address } from "../address";
import { IPlainTransactionObject, ITransaction } from "../interface";
import { IContractResultItem, ITransactionEvent, ITransactionOnNetwork } from "../interfaceOfNetwork";
import { ResultsParser } from "../smartcontracts";
Expand Down Expand Up @@ -25,9 +26,11 @@ export class TransactionsConverter {
chainID: transaction.chainID.valueOf(),
version: transaction.version,
options: transaction.options == 0 ? undefined : transaction.options,
relayer: !transaction.relayer || transaction.relayer.isEmpty() ? undefined : transaction.relayer.toBech32(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have relayer and guardian done in the same way. Which way should we choose?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, now I see, cannot be same as guardian, since that's a string 👍

guardian: transaction.guardian ? transaction.guardian : undefined,
signature: this.toHexOrUndefined(transaction.signature),
guardianSignature: this.toHexOrUndefined(transaction.guardianSignature),
relayerSignature: this.toHexOrUndefined(transaction.relayerSignature),
};

return plainObject;
Expand All @@ -46,6 +49,7 @@ export class TransactionsConverter {
nonce: BigInt(object.nonce),
value: BigInt(object.value || ""),
receiver: object.receiver,
relayer: object.relayer ? Address.newFromBech32(object.relayer) : Address.empty(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have it the same as guardian? If so, which way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, now I see, cannot be same as guardian, since that's a string 👍

receiverUsername: this.bufferFromBase64(object.receiverUsername).toString(),
sender: object.sender,
senderUsername: this.bufferFromBase64(object.senderUsername).toString(),
Expand All @@ -58,6 +62,7 @@ export class TransactionsConverter {
options: Number(object.options),
signature: this.bufferFromHex(object.signature),
guardianSignature: this.bufferFromHex(object.guardianSignature),
relayerSignature: this.bufferFromHex(object.relayerSignature),
});

return transaction;
Expand Down
6 changes: 4 additions & 2 deletions src/converters/transactionsConverters.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { assert } from "chai";
import { Address } from "../address";
import {
ContractResultItem,
ContractResults,
Expand All @@ -7,8 +9,6 @@ import {
TransactionLogsOnNetwork,
TransactionOnNetwork,
} from "../networkProviders";
import { assert } from "chai";
import { Address } from "../address";
import { Transaction } from "../transaction";
import {
SmartContractCallOutcome,
Expand Down Expand Up @@ -58,7 +58,9 @@ describe("test transactions converter", async () => {
options: undefined,
guardian: undefined,
signature: undefined,
relayer: undefined,
guardianSignature: undefined,
relayerSignature: undefined,
});
});

Expand Down
5 changes: 5 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import BigNumber from "bignumber.js";
import { Address } from "./address";
import { ITransactionOnNetwork } from "./interfaceOfNetwork";

export interface ITransactionFetcher {
Expand All @@ -16,6 +17,7 @@ export interface IPlainTransactionObject {
receiverUsername?: string;
senderUsername?: string;
guardian?: string;
relayer?: string;
gasPrice: number;
gasLimit: number;
data?: string;
Expand All @@ -24,6 +26,7 @@ export interface IPlainTransactionObject {
options?: number;
signature?: string;
guardianSignature?: string;
relayerSignature?: string;
}

export interface ISignature {
Expand Down Expand Up @@ -102,6 +105,8 @@ export interface ITransaction {
version: number;
options: number;
guardian: string;
relayer?: Address;
signature: Uint8Array;
guardianSignature: Uint8Array;
relayerSignature?: Uint8Array;
}
64 changes: 64 additions & 0 deletions src/proto/compiled.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/proto/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* Serializes a Transaction object to a Buffer. Handles low-level conversion logic and field-mappings as well.
*/
serializeTransaction(transaction: Transaction): Buffer {
const proto = require("./compiled").proto;

Check warning on line 19 in src/proto/serializer.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Require statement not part of import statement

const protoTransaction = this.convertToProtoMessage(transaction);
const encoded = proto.Transaction.encode(protoTransaction).finish();
Expand All @@ -26,7 +26,7 @@
}

private convertToProtoMessage(transaction: ITransaction) {
const proto = require("./compiled").proto;

Check warning on line 29 in src/proto/serializer.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Require statement not part of import statement

const receiverPubkey = new Address(transaction.receiver).getPublicKey();
const senderPubkey = new Address(transaction.sender).getPublicKey();
Expand Down Expand Up @@ -60,6 +60,11 @@
protoTransaction.GuardianSignature = transaction.guardianSignature;
}

if (transaction.relayer && !transaction.relayer.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can refactor to have a mini private function isRelayedTransaction (for symmetry) .

protoTransaction.Relayer = transaction.relayer.getPublicKey();
protoTransaction.RelayerSignature = transaction.relayerSignature;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's extract to a function similar to the guardian check above, and also check if relayer signature is set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we discussed about this internally and said it is not needed


return protoTransaction;
}

Expand Down
2 changes: 2 additions & 0 deletions src/proto/transaction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ message Transaction {
uint32 Options = 13;
bytes GuardianAddr = 14;
bytes GuardianSignature = 15;
bytes Relayer = 16;
bytes RelayerSignature = 17;
}
42 changes: 41 additions & 1 deletion src/transaction.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { UserPublicKey, UserVerifier } from "./wallet";
import BigNumber from "bignumber.js";
import { assert } from "chai";
import { Address } from "./address";
Expand All @@ -10,6 +9,7 @@ import { TokenTransfer } from "./tokens";
import { Transaction } from "./transaction";
import { TransactionComputer } from "./transactionComputer";
import { TransactionPayload } from "./transactionPayload";
import { UserPublicKey, UserVerifier } from "./wallet";

describe("test transaction", async () => {
let wallets: Record<string, TestWallet>;
Expand Down Expand Up @@ -751,4 +751,44 @@ describe("test transaction", async () => {
assert.equal(isSignedByAlice, true);
assert.equal(isSignedByBob, false);
});

it("should serialize transaction with relayer", async () => {
const transaction = new Transaction({
chainID: networkConfig.ChainID,
sender: wallets.alice.address.bech32(),
receiver: wallets.alice.address.bech32(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use toBech32(), here and bellow.

relayer: wallets.bob.address,
gasLimit: 50000n,
value: 0n,
version: 2,
nonce: 89n,
});

const serializedTransactionBytes = transactionComputer.computeBytesForSigning(transaction);
const serializedTransaction = Buffer.from(serializedTransactionBytes).toString();

assert.equal(
serializedTransaction,
`{"nonce":89,"value":"0","receiver":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","sender":"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th","gasPrice":1000000000,"gasLimit":50000,"chainID":"D","version":2,"relayer":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"}`,
);
});

it("should test relayed v3", async () => {
const transaction = new Transaction({
chainID: networkConfig.ChainID,
sender: wallets.alice.address.bech32(),
receiver: wallets.alice.address.bech32(),
senderUsername: "alice",
receiverUsername: "bob",
gasLimit: 80000n,
value: 0n,
version: 2,
nonce: 89n,
data: Buffer.from("hello"),
});

assert.isFalse(transactionComputer.isRelayedV3Transaction(transaction));
transaction.relayer = wallets.carol.address;
assert.isTrue(transactionComputer.isRelayedV3Transaction(transaction));
});
});
17 changes: 15 additions & 2 deletions src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
INonce,
IPlainTransactionObject,
ISignature,
ITransaction,
ITransactionOptions,
ITransactionPayload,
ITransactionValue,
Expand All @@ -20,8 +19,8 @@ import {
import { INetworkConfig } from "./interfaceOfNetwork";
import { TransactionOptions, TransactionVersion } from "./networkParams";
import { interpretSignatureAsBuffer } from "./signature";
import { TransactionPayload } from "./transactionPayload";
import { TransactionComputer } from "./transactionComputer";
import { TransactionPayload } from "./transactionPayload";

/**
* An abstraction for creating and signing transactions.
Expand Down Expand Up @@ -92,6 +91,11 @@ export class Transaction {
*/
public guardian: string;

/**
* The relayer, in address format, next version all the other addresses will not be string anymore.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be rephrased.

The relayer address.
Note: in the next major version, `sender`, `receiver` and `guardian` will also have the type `Address`, instead of `string`.

*/
public relayer?: Address;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this doesn't need to be optional. Make it optional on the constructor and if not provided, set to Address.empty()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/**
* The signature.
*/
Expand All @@ -102,6 +106,11 @@ export class Transaction {
*/
public guardianSignature: Uint8Array;

/**
* The signature of the guardian.
*/
public relayerSignature: Uint8Array;

/**
* Creates a new Transaction object.
*/
Expand All @@ -110,6 +119,7 @@ export class Transaction {
value?: ITransactionValue | bigint;
sender: IAddress | string;
receiver: IAddress | string;
relayer?: Address;
senderUsername?: string;
receiverUsername?: string;
gasPrice?: IGasPrice | bigint;
Expand All @@ -121,6 +131,7 @@ export class Transaction {
guardian?: IAddress | string;
signature?: Uint8Array;
guardianSignature?: Uint8Array;
relayerSignature?: Uint8Array;
}) {
this.nonce = BigInt(options.nonce?.valueOf() || 0n);
// We still rely on "bigNumber" for value, because client code might be passing a BigNumber object as a legacy "ITransactionValue",
Expand All @@ -137,9 +148,11 @@ export class Transaction {
this.version = Number(options.version?.valueOf() || TRANSACTION_VERSION_DEFAULT);
this.options = Number(options.options?.valueOf() || TRANSACTION_OPTIONS_DEFAULT);
this.guardian = options.guardian ? this.addressAsBech32(options.guardian) : "";
this.relayer = options.relayer?.isEmpty() ? undefined : options.relayer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, now I see, cannot be same as guardian, since that's a string 👍


this.signature = options.signature || Buffer.from([]);
this.guardianSignature = options.guardianSignature || Buffer.from([]);
this.relayerSignature = options.relayerSignature || Buffer.from([]);
}

private addressAsBech32(address: IAddress | string): string {
Expand Down
Loading
Loading