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

Add functionality to extend tx types #6493

Merged
42 changes: 41 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2089,19 +2089,59 @@ If there are any bugs, improvements, optimizations or any new feature proposal f

- Added `ALL_EVENTS` and `ALL_EVENTS_ABI` constants, `SendTransactionEventsBase` type, `decodeEventABI` method (#6410)

#### web3-eth-accounts

- Added public function `privateKeyToPublicKey`
- Added exporting `BaseTransaction` from the package (#6493)
- Added exporting `txUtils` from the package (#6493)

#### web3-types

- Interface `EventLog` was added. (#6410)

#### web3-utils

- As a replacment of the node EventEmitter, a custom `EventEmitter` has been implemented and exported. (#6398)

### Fixed

#### web3-core

- Fix the issue: "Uncaught TypeError: Class extends value undefined is not a constructor or null #6371". (#6398)

#### web3-eth

- Ensure provider.supportsSubscriptions exists before watching by subscription (#6440)
- Fixed `withdrawalsSchema.address` property type `bytes32` to `address` (#6470)
- Fixed param sent to `checkRevertBeforeSending` in `sendSignedTransaction`

#### web3-eth-accounts

- Fixed `recover` function, `v` will be normalized to value 0,1 (#6344)

#### web3-providers-http

- Fix issue lquixada/cross-fetch#78, enabling to run web3.js in service worker (#6463)

#### web3-validator

- Multi-dimensional arrays are now handled properly when parsing ABIs

### Changed

#### web3-core

- defaultTransactionType is now type 0x2 instead of 0x0 (#6282)
- Allows formatter to parse large base fee (#6456)
- The package now uses `EventEmitter` from `web3-utils` that works in node envrioment as well as in the browser. (#6398)

#### web3-eth

- Transactions will now default to type 2 transactions instead of type 0, similar to 1.x version. (#6282)

#### web3-eth-contract

- The `events` property was added to the `receipt` object (#6410)

#### web3-providers-http

- Bump cross-fetch to version 4 (#6463).
25 changes: 25 additions & 0 deletions docs/docs/guides/web3_plugin_guide/plugin_authors.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,31 @@ It is important to note that the plugin name should be structured as `@<organiza

When your users install your plugin, this will allow the package manager to make use of the user installed `web3` if available and if the version satisfies the version constraints instead of installing it's own version of `web3`.

## Add New Transaction Type

Furthermore, you have the flexibility to expand your range of transaction types, enhancing compatibility with the `web3.js` library.


```typescript
// create new TransactionType class which extends BaseTransaction class
import { BaseTransaction } from 'web3-eth-accounts';
const TRANSACTION_TYPE = 15;
class SomeNewTxTypeTransaction extends BaseTransaction {
// ...
}

// create new plugin and add `SomeNewTxTypeTransaction` to the library
import { Web3EthPluginBase } from 'web3';

class SomeNewTxTypeTransactionPlugin extends Web3PluginBase {
public pluginNamespace = 'someNewTxTypeTransaction';
public constructor() {
super();
TransactionFactory.registerTransactionType(TRANSACTION_TYPE, SomeNewTxTypeTransaction);
}
}
```

## Extending `Web3PluginBase`

Your plugin class should `extend` the `Web3PluginBase` abstract class. This class `extends` [Web3Context](/api/web3-core/class/Web3Context) and when the user registers your plugin with a class, your plugin's `Web3Context` will point to the module's `Web3Context` giving your plugin access to things such as user configured [requestManager](/api/web3-core/class/Web3Context#requestManager) and [accountProvider](/api/web3-core/class/Web3Context#accountProvider).
Expand Down
2 changes: 2 additions & 0 deletions packages/web3-eth-accounts/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ Documentation:
### Added

- Added public function `privateKeyToPublicKey`
- Added exporting `BaseTransaction` from the package (#6493)
- Added exporting `txUtils` from the package (#6493)

### Fixed

Expand Down
29 changes: 21 additions & 8 deletions packages/web3-eth-accounts/src/tx/baseTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,9 @@
import { Numbers } from 'web3-types';
import { bytesToHex } from 'web3-utils';
import { MAX_INTEGER, MAX_UINT64, SECP256K1_ORDER_DIV_2, secp256k1 } from './constants.js';
import {
Chain,
Common,
Hardfork,
toUint8Array,
uint8ArrayToBigInt,
unpadUint8Array,
} from '../common/index.js';
import { toUint8Array, uint8ArrayToBigInt, unpadUint8Array } from '../common/utils.js';
import { Common } from '../common/common.js';
import { Hardfork, Chain } from '../common/enums.js';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fix dependency cycle

import type {
AccessListEIP2930TxData,
AccessListEIP2930ValuesArray,
Expand Down Expand Up @@ -565,4 +560,22 @@

return { r, s, v };
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static fromSerializedTx(
jdevcs marked this conversation as resolved.
Show resolved Hide resolved
// @ts-expect-error unused variable
serialized: Uint8Array,
// @ts-expect-error unused variable
opts: TxOptions = {},

Check warning on line 569 in packages/web3-eth-accounts/src/tx/baseTransaction.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-accounts/src/tx/baseTransaction.ts#L569

Added line #L569 was not covered by tests
// eslint-disable-next-line @typescript-eslint/no-empty-function
): any {}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static fromTxData(
jdevcs marked this conversation as resolved.
Show resolved Hide resolved
// @ts-expect-error unused variable
txData: any,
// @ts-expect-error unused variable
opts: TxOptions = {},

Check warning on line 578 in packages/web3-eth-accounts/src/tx/baseTransaction.ts

View check run for this annotation

Codecov / codecov/patch

packages/web3-eth-accounts/src/tx/baseTransaction.ts#L578

Added line #L578 was not covered by tests
// eslint-disable-next-line @typescript-eslint/no-empty-function
): any {}
}
2 changes: 2 additions & 0 deletions packages/web3-eth-accounts/src/tx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ export { FeeMarketEIP1559Transaction } from './eip1559Transaction.js';
export { AccessListEIP2930Transaction } from './eip2930Transaction.js';
export { Transaction } from './legacyTransaction.js';
export { TransactionFactory } from './transactionFactory.js';
export { BaseTransaction } from './baseTransaction.js';
export * as txUtils from './utils.js';
export * from './types.js';
34 changes: 32 additions & 2 deletions packages/web3-eth-accounts/src/tx/transactionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
import { Numbers } from 'web3-types';
import { toUint8Array, uint8ArrayToBigInt } from '../common/utils.js';
import { FeeMarketEIP1559Transaction } from './eip1559Transaction.js';
import { AccessListEIP2930Transaction } from './eip2930Transaction.js';
Expand All @@ -26,13 +27,28 @@ import type {
TxData,
TxOptions,
} from './types.js';
import { BaseTransaction } from './baseTransaction.js';

const extraTxTypes: Map<Numbers, typeof BaseTransaction<unknown>> = new Map();

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class TransactionFactory {
// It is not possible to instantiate a TransactionFactory object.
// eslint-disable-next-line @typescript-eslint/no-empty-function, no-useless-constructor
private constructor() {}

public static typeToInt(txType: Numbers) {
return Number(uint8ArrayToBigInt(toUint8Array(txType)));
}

public static registerTransactionType<NewTxTypeClass extends typeof BaseTransaction<unknown>>(
type: Numbers,
txClass: NewTxTypeClass,
) {
const txType = TransactionFactory.typeToInt(type);
extraTxTypes.set(txType, txClass);
}

/**
* Create a transaction from a `txData` object
*
Expand All @@ -47,7 +63,7 @@ export class TransactionFactory {
// Assume legacy transaction
return Transaction.fromTxData(txData as TxData, txOptions);
}
const txType = Number(uint8ArrayToBigInt(toUint8Array(txData.type)));
const txType = TransactionFactory.typeToInt(txData.type);
if (txType === 0) {
return Transaction.fromTxData(txData as TxData, txOptions);
}
Expand All @@ -66,6 +82,11 @@ export class TransactionFactory {
txOptions,
);
}
const ExtraTransaction = extraTxTypes.get(txType);
if (ExtraTransaction?.fromTxData) {
return ExtraTransaction.fromTxData(txData, txOptions) as TypedTransaction;
}

throw new Error(`Tx instantiation with type ${txType} not supported`);
}

Expand All @@ -86,8 +107,17 @@ export class TransactionFactory {
return AccessListEIP2930Transaction.fromSerializedTx(data, txOptions);
case 2:
return FeeMarketEIP1559Transaction.fromSerializedTx(data, txOptions);
default:
default: {
const ExtraTransaction = extraTxTypes.get(Number(data[0]));
if (ExtraTransaction?.fromSerializedTx) {
return ExtraTransaction.fromSerializedTx(
data,
txOptions,
) as TypedTransaction;
}

throw new Error(`TypedTransaction with ID ${data[0]} unknown`);
}
}
} else {
return Transaction.fromSerializedTx(data, txOptions);
Expand Down
12 changes: 6 additions & 6 deletions packages/web3-eth-accounts/test/unit/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
recoverTransaction,
sign,
signTransaction,
privateKeyToPublicKey
privateKeyToPublicKey,
} from '../../src/account';
import {
invalidDecryptData,
Expand Down Expand Up @@ -98,8 +98,8 @@ describe('accounts', () => {
it.each(validPrivateKeyToPublicKeyData)('%s', (privateKey, isCompressed, output) => {
expect(privateKeyToPublicKey(privateKey, isCompressed)).toEqual(output);
});
})
})
});
});

describe('Signing and Recovery of Transaction', () => {
it.each(transactionsTestData)('sign transaction', async txData => {
Expand Down Expand Up @@ -228,8 +228,8 @@ describe('accounts', () => {

describe('valid signatures for recover', () => {
it.each(validRecover)('&s', (data, signature) => {
recover(data, signature)
})
})
recover(data, signature);
});
});
});
});
12 changes: 6 additions & 6 deletions packages/web3-eth-accounts/test/unit/common/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@ describe('[Utils/Parse]', () => {
merge: '0x013fd1b5',
};

it('should parse geth params file', async () => {
it('should parse geth params file', () => {
const params = parseGethGenesis(testnet, 'rinkeby');
expect(params.genesis.nonce).toBe('0x0000000000000042');
});

it('should throw with invalid Spurious Dragon blocks', async () => {
it('should throw with invalid Spurious Dragon blocks', () => {
expect(() => {
parseGethGenesis(invalidSpuriousDragon, 'bad_params');
}).toThrow();
});

it('should import poa network params correctly', async () => {
it('should import poa network params correctly', () => {
let params = parseGethGenesis(poa, 'poa');
expect(params.genesis.nonce).toBe('0x0000000000000000');
expect(params.consensus).toEqual({
Expand All @@ -67,18 +67,18 @@ describe('[Utils/Parse]', () => {
expect(params.hardfork).toEqual(Hardfork.London);
});

it('should generate expected hash with london block zero and base fee per gas defined', async () => {
it('should generate expected hash with london block zero and base fee per gas defined', () => {
const params = parseGethGenesis(postMerge, 'post-merge');
expect(params.genesis.baseFeePerGas).toEqual(postMerge.baseFeePerGas);
});

it('should successfully parse genesis file with no extraData', async () => {
it('should successfully parse genesis file with no extraData', () => {
const params = parseGethGenesis(noExtraData, 'noExtraData');
expect(params.genesis.extraData).toBe('0x');
expect(params.genesis.timestamp).toBe('0x10');
});

it('should successfully parse kiln genesis and set forkhash', async () => {
it('should successfully parse kiln genesis and set forkhash', () => {
const common = Common.fromGethGenesis(gethGenesisKiln, {
chain: 'customChain',
genesisHash: hexToBytes(
Expand Down
49 changes: 49 additions & 0 deletions packages/web3-eth-accounts/test/unit/tx/registerNewTx.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

import { TransactionFactory } from '../../../src/tx/transactionFactory';
import { BaseTransaction } from '../../../src/tx/baseTransaction';
import { TxData, TxOptions } from '../../../src/tx';

describe('Register new TX', () => {
it('validateCannotExceedMaxInteger()', () => {
const TYPE = 20;
// @ts-expect-error not implement all methods
class SomeNewTxType extends BaseTransaction<any> {
public constructor(txData: TxData, opts: TxOptions = {}) {
super({ ...txData, type: TYPE }, opts);
}
public static fromTxData() {
return 'new fromTxData';
}
public static fromSerializedTx() {
return 'new fromSerializedData';
}
}
TransactionFactory.registerTransactionType(TYPE, SomeNewTxType);
const txData = {
from: '0x',
to: '0x',
value: '0x1',
type: TYPE,
};
expect(TransactionFactory.fromTxData(txData)).toBe('new fromTxData');
expect(TransactionFactory.fromSerializedData(new Uint8Array([TYPE, 10]))).toBe(
'new fromSerializedData',
);
});
});
24 changes: 24 additions & 0 deletions packages/web3-eth-accounts/test/unit/tx/staticMethods.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
import { BaseTransaction } from '../../../src/tx/baseTransaction';

describe('[BaseTransaction]', () => {
it('Initialization', () => {
expect(typeof BaseTransaction.fromTxData).toBe('function');
expect(typeof BaseTransaction.fromSerializedTx).toBe('function');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ describe('[TransactionFactory]: Basic functions', () => {
}
});

it('fromBlockBodyData() -> error case', () => {
expect(() => {
// @ts-expect-error incorrect param type
TransactionFactory.fromBlockBodyData('');
}).toThrow();
});

it('fromTxData() -> success cases', () => {
for (const txType of txTypes) {
const tx = TransactionFactory.fromTxData({ type: txType.type }, { common });
Expand Down