Skip to content

Commit

Permalink
Add functionality to extend tx types (#6493)
Browse files Browse the repository at this point in the history
* draft

* finish

* lint

* add documentation

* fix unit tests

* add unit tests

* add unit tests

* increase coverage

* increase coverage

* fix changelog syncing

* fix test
  • Loading branch information
avkos committed Oct 17, 2023
1 parent 10d1f12 commit 70d1957
Show file tree
Hide file tree
Showing 18 changed files with 781 additions and 26 deletions.
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 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
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';
import type {
AccessListEIP2930TxData,
AccessListEIP2930ValuesArray,
Expand Down Expand Up @@ -565,4 +560,22 @@ export abstract class BaseTransaction<TransactionObject> {

return { r, s, v };
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static fromSerializedTx(
// @ts-expect-error unused variable
serialized: Uint8Array,
// @ts-expect-error unused variable
opts: TxOptions = {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
): any {}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static fromTxData(
// @ts-expect-error unused variable
txData: any,
// @ts-expect-error unused variable
opts: TxOptions = {},
// 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

0 comments on commit 70d1957

Please sign in to comment.