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

[TS SDK] Explore http2 #7432

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
269 changes: 269 additions & 0 deletions ecosystem/typescript/sdk/examples/typescript/adobe-http2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import { AptosAccount, AptosClient, BCS, HexString, TxnBuilderTypes } from "../../../dist";
import { Timer } from "timer-node";
import { fetch } from "@adobe/fetch";
import { exit } from "process";

const FULLNODE_URL = "http2://0.0.0.0:8080/v1";
const FAUCET_URL = "http2://0.0.0.0:8081";

async function main() {
const timer = new Timer();

const accountsCount = 50;
const firstPass = 100;
const readAmplification = 100;
let accountSequenceNumber: AccountSequenceNumbers | null = null;

console.log("starting...");
timer.start();
// create accounts
const accounts: AptosAccount[] = [];
const recipients: AptosAccount[] = [];
for (let i = 0; i < accountsCount; i++) {
accounts.push(new AptosAccount());
recipients.push(new AptosAccount());
}
console.log("accounts created");
console.log(timer.time());

// funds accounts
const funds: Promise<any>[] = [];

for (let i = 0; i < accounts.length; i++) {
funds.push(faucet(accounts[i], 100000000));
}
for (let i = 0; i < recipients.length; i++) {
funds.push(faucet(recipients[i], 0));
}
await Promise.all(funds);
// sleeps to let faucet do its work without the need to implement
// waitForTransaction in this new client
await sleep(15000); // 15 seconds
console.log("accounts funded");
console.log(timer.time());

// read accounts
const balances: string[] = [];
for (let j = 0; j < readAmplification; j++) {
for (let i = 0; i < accounts.length; i++) {
balances.push(`${FULLNODE_URL}/accounts/${accounts[i].address().hex()}`);
}
}

// send requests
const responses = await Promise.all(
balances.map((url) =>
fetch(url, {
headers: {
"content-type": "application/json",
},
}),
),
);

// read bodies
await Promise.all(responses.map((resp) => resp.json()));

//await Promise.all(balances);
console.log("accounts checked");
console.log(timer.time());

// initialize accounts with sequence number

// array to hold the sequence number class initialization of an account
const accountSequenceNumbers: AccountSequenceNumbers[] = [];
// array to hold prmoises to fetch account current sequence number
const awaitSequenceNumbers: Promise<void>[] = [];
for (let i = 0; i < accounts.length; i++) {
accountSequenceNumber = new AccountSequenceNumbers(accounts[i]);
awaitSequenceNumbers.push(accountSequenceNumber.initialize());
accountSequenceNumbers.push(accountSequenceNumber);
}

await Promise.all(awaitSequenceNumbers);
console.log("accounts initialized");
console.log(timer.time());

// submit transactions
let transactionsHashes: string[] = [];
for (let i = 0; i < firstPass; i++) {
for (let j = 0; j < accountsCount; j++) {
let sender = accounts[j];
let recipient = recipients[j].address().hex();
let sequenceNumber: bigint = await accountSequenceNumbers[j].nextSequenceNumber();
let txnHash = await transafer(sender, recipient, sequenceNumber, 1);
transactionsHashes.push(txnHash);
}
}

transactionsHashes = await Promise.all(transactionsHashes);

console.log("transactions submitted");
console.log(timer.time());
// check for transactions
const waitFor: Promise<void>[] = [];
for (let i = 0; i < transactionsHashes.length; i++) {
waitFor.push(accountSequenceNumber!.synchronize());
}

await Promise.all(waitFor);
console.log("transactions commited");
console.log(timer.time());

exit(0);
}

async function transafer(sender: AptosAccount, recipient: string, sequenceNumber: bigint, amount: number) {
const token = new TxnBuilderTypes.TypeTagStruct(TxnBuilderTypes.StructTag.fromString("0x1::aptos_coin::AptosCoin"));

const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::coin",
"transfer",
[token],
[BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(recipient)), BCS.bcsSerializeUint64(amount)],
),
);

const rawTransaction = new TxnBuilderTypes.RawTransaction(
// Transaction sender account address
TxnBuilderTypes.AccountAddress.fromHex(sender.address()),
BigInt(sequenceNumber),
entryFunctionPayload,
// Max gas unit to spend
BigInt(200000),
// Gas price per unit
BigInt(100),
// Expiration timestamp. Transaction is discarded if it is not executed within 20 seconds from now.
BigInt(Math.floor(Date.now() / 1000) + 20),
new TxnBuilderTypes.ChainId(4),
);

const bcsTxn = AptosClient.generateBCSTransaction(sender, rawTransaction);
const txn = await submitTransaction(bcsTxn);
return txn.hash;
}

async function get(path: string): Promise<any> {
console.log(path);
const response = await fetch(`${FULLNODE_URL}/${path}`, {
headers: {
"content-type": "application/json",
},
});
const res = await response.json();
return res;
}

async function submitTransaction(bcsTxn: any): Promise<any> {
const response = await fetch(`${FULLNODE_URL}/transactions`, {
method: "POST",
body: Buffer.from(bcsTxn),
headers: {
"content-type": "application/x.aptos.signed_transaction+bcs",
},
});
const res = await response.json();
return res;
}

async function faucet(account: AptosAccount, amount: number) {
const response = await fetch(
`${FAUCET_URL}/mint?address=${HexString.ensure(account.address()).noPrefix()}&amount=${amount}`,
{
method: "POST",
},
);
const res = await response.json();
return res;
}

async function sleep(ms: number): Promise<void> {
return new Promise<void>((resolve) => setTimeout(resolve, ms));
}

class AccountSequenceNumbers {
account: AptosAccount;
lastUncommintedNumber: BCS.Uint64 | null = null;
currentNumber: BCS.Uint64 | null = null;
lock = false;
maximumInFlight = 50;
sleepTime = 10;
maxWaitTime = 30; // in seconds

constructor(acccount: AptosAccount) {
this.account = acccount;
}

async initialize(): Promise<void> {
const data = await get(`accounts/${this.account.address().hex()}`);
this.currentNumber = BigInt(data.sequence_number);
this.lastUncommintedNumber = BigInt(data.sequence_number);
}

async update() {
const { sequence_number } = await get(`accounts/${this.account.address().hex()}`);
this.lastUncommintedNumber = BigInt(sequence_number);
return this.lastUncommintedNumber;
}

async nextSequenceNumber(): Promise<bigint> {
/*
`lock` is used to prevent multiple coroutines from accessing a shared resource at the same time, which can result in race conditions and data inconsistency.
This implementation is not as robust as using a proper lock implementation
like `async-mutex` because it relies on busy waiting to acquire the lock,
which can be less efficient and may not work well in all scenarios
*/
while (this.lock) {
await sleep(this.sleepTime);
}

this.lock = true;
let nextNumber = BigInt(0);
try {
if (this.lastUncommintedNumber === null || this.currentNumber === null) {
await this.initialize();
}

if (this.currentNumber! - this.lastUncommintedNumber! >= this.maximumInFlight) {
await this.update();

const startTime = Math.floor(Date.now() / 1000);
while (this.lastUncommintedNumber! - this.currentNumber! >= this.maximumInFlight) {
await sleep(this.sleepTime);
if (Math.floor(Date.now() / 1000) - startTime > this.maxWaitTime) {
console.warn(`Waited over 30 seconds for a transaction to commit, resyncing ${this.account.address()}`);
await this.initialize();
} else {
await this.update();
}
}
}
nextNumber = this.currentNumber!;
this.currentNumber!++;
} catch (e) {
console.error("error", e);
} finally {
this.lock = false;
}
return nextNumber;
}

async synchronize() {
if (this.lastUncommintedNumber == this.currentNumber) return;

await this.update();
const startTime = Math.floor(Date.now() / 1000);
while (this.lastUncommintedNumber != this.currentNumber) {
if (Math.floor(Date.now() / 1000) - startTime > this.maxWaitTime) {
console.warn(`Waited over 30 seconds for a transaction to commit, resyncing ${this.account.address()}`);
await this.initialize();
} else {
await sleep(this.sleepTime);
await this.update();
}
}
}
}

main();
Loading