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 setFee and setFeePerWU #1968

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased](https://github.com/o1-labs/o1js/compare/b857516...HEAD)

### Added
- `setFee` and `setFeePerWU` for `Transaction` and `PendingTransaction`

### Changed
- Sort order for actions now includes the transaction sequence number and the exact account id sequence https://github.com/o1-labs/o1js/pull/1917

Expand Down Expand Up @@ -367,7 +370,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- `this.sender.getAndRequireSignature()` which requires a signature from the sender's public key and therefore proves that whoever created the transaction really owns the sender account
- `Reducer.reduce()` requires the maximum number of actions per method as an explicit (optional) argument https://github.com/o1-labs/o1js/pull/1450
- The default value is 1 and should work for most existing contracts
- `new UInt64()` and `UInt64.from()` no longer unsafely accept a field element as input. https://github.com/o1-labs/o1js/pull/1438 [@julio4](https://github.com/julio4)
- `new UInt64()` and `UInt64.from()` no longer unsafely accept a field element as input. https://github.com/o1-labs/o1js/pull/1438 [@julio4](https://github.com/julio4)
As a replacement, `UInt64.Unsafe.fromField()` was introduced
- This prevents you from accidentally creating a `UInt64` without proving that it fits in 64 bits
- Equivalent changes were made to `UInt32`
Expand Down Expand Up @@ -1142,7 +1145,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- **Recursive proofs**. RFC: https://github.com/o1-labs/o1js/issues/89, PRs: https://github.com/o1-labs/o1js/pull/245 https://github.com/o1-labs/o1js/pull/250 https://github.com/o1-labs/o1js/pull/261
- Enable smart contract methods to take previous proofs as arguments, and verify them in the circuit
- Add `ZkProgram`, a new primitive which represents a collection of circuits that produce instances of the same proof. So, it's a more general version of `SmartContract`, without any of the Mina-related API.
- Add `ZkProgram`, a new primitive which represents a collection of circuits that produce instances of the same proof. So, it's a more general version of `SmartContract`, without any of the Mina-related API.
`ZkProgram` is suitable for rollup-type systems and offchain usage of Pickles + Kimchi.
- **zkApp composability** -- calling other zkApps from inside zkApps. RFC: https://github.com/o1-labs/o1js/issues/303, PRs: https://github.com/o1-labs/o1js/pull/285, https://github.com/o1-labs/o1js/pull/296, https://github.com/o1-labs/o1js/pull/294, https://github.com/o1-labs/o1js/pull/297
- **Events** support via `SmartContract.events`, `this.emitEvent`. RFC: https://github.com/o1-labs/o1js/issues/248, PR: https://github.com/o1-labs/o1js/pull/272
Expand Down
4 changes: 2 additions & 2 deletions flake.lock

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

2 changes: 2 additions & 0 deletions src/lib/mina/local-blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ async function LocalBlockchain({
status,
errors,
transaction: txn.transaction,
setFee: txn.setFee,
setFeePerWU: txn.setFeePerWU,
hash,
toJSON: txn.toJSON,
toPretty: txn.toPretty,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/mina/mina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ function Network(
data: response?.data,
errors: updatedErrors,
transaction: txn.transaction,
setFee : txn.setFee,
setFeePerWU : txn.setFeePerWU,
hash,
toJSON: txn.toJSON,
toPretty: txn.toPretty,
Expand Down
66 changes: 66 additions & 0 deletions src/lib/mina/transaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
UInt64,
SmartContract,
Mina,
AccountUpdate,
method,
} from 'o1js';

class MyContract extends SmartContract {
@method async shouldMakeCompileThrow() {
this.network.blockchainLength.get();
}
}

let contractAccount: Mina.TestPublicKey;
let contract: MyContract;
let feePayer: Mina.TestPublicKey;

describe('transactions', () => {
beforeAll(async () => {
// set up local blockchain, create contract account keys, deploy the contract
let Local = await Mina.LocalBlockchain({ proofsEnabled: false });
Mina.setActiveInstance(Local);
[feePayer] = Local.testAccounts;

contractAccount = Mina.TestPublicKey.random();
contract = new MyContract(contractAccount);

let tx = await Mina.transaction(feePayer, async () => {
AccountUpdate.fundNewAccount(feePayer);
await contract.deploy();
});
tx.sign([feePayer.key, contractAccount.key]).send();
});

it('setFee should not change nonce', async () => {
let tx = await Mina.transaction(feePayer, async () => {
contract.requireSignature();
AccountUpdate.attachToTransaction(contract.self);
});
let nonce = tx.transaction.feePayer.body.nonce;
let promise = await tx.sign([feePayer.key, contractAccount.key]).send();
let new_fee = promise.setFee(new UInt64(100));
new_fee.sign([feePayer.key,contractAccount.key]);
// second send is rejected for using the same nonce
await expect((new_fee.send()))
.rejects
.toThrowError("Account_nonce_precondition_unsatisfied");
// check that tx was applied, by checking nonce was incremented
expect((await new_fee).transaction.feePayer.body.nonce).toEqual(nonce);
});

it('Second tx should work when first not sent', async () => {
let tx = await Mina.transaction(feePayer, async () => {
contract.requireSignature();
AccountUpdate.attachToTransaction(contract.self);
});
let nonce = tx.transaction.feePayer.body.nonce;
let promise = tx.sign([feePayer.key, contractAccount.key]);
let new_fee = promise.setFee(new UInt64(100));
await new_fee.sign([feePayer.key,contractAccount.key]).prove();
await new_fee.send();
// check that tx was applied, by checking nonce was incremented
expect((await new_fee).transaction.feePayer.body.nonce).toEqual(nonce);
});
});
48 changes: 48 additions & 0 deletions src/lib/mina/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,25 @@ type Transaction<
* ```
*/
safeSend(): Promise<PendingTransaction | RejectedTransaction>;

/**
* Modifies a transaction to set the fee to the new fee provided. Because this change invalidates proofs and signatures both are removed. The nonce is not increased so sending both transitions will not risk both being accepted.
* @returns {TransactionPromise<false,false>} The same transaction with the new fee and the proofs and signatures removed.
* @example
* ```ts
* tx.send();
* // Waits for some time and decide to resend with a higher fee
*
* tx.setFee(newFee);
* await tx.sing([privateKey]).prove();
* await tx.send();
* ```
*/
setFee(newFee:UInt64) : TransactionPromise<false,false>;
/**
* setFeePerWU behaves identically to {@link setFee} but the fee is given per Account Update in the transaction. This is useful because nodes prioritize transactions by fee per weight unit.
*/
setFeePerWU(newFeePerWU:UInt64) : TransactionPromise<false,false>;
} & (Proven extends false
? {
/**
Expand Down Expand Up @@ -250,6 +269,15 @@ type PendingTransaction = Pick<
* ```
*/
errors: string[];

/**
* setFee is the same as {@link Transaction.setFee(newFee)} but for a {@link PendingTransaction}.
*/
setFee(newFee:UInt64):TransactionPromise<false,false>;
/**
* setFeePerWU is the same as {@link Transaction.setFeeWU(newFeePerWU)} but for a {@link PendingTransaction}.
*/
setFeePerWU(newFeePerWU:UInt64):TransactionPromise<false,false>;
};

/**
Expand Down Expand Up @@ -544,6 +572,26 @@ function newTransaction(transaction: ZkappCommand, proofsEnabled?: boolean) {
}
return pendingTransaction;
},
setFeePerWU(newFeePerWU:UInt64) {
//Currently WU is just the number of accountUpdates + 1
//https://github.com/MinaProtocol/mina/blob/a0a2adf6b1dce7af889250ff469a35ae4afa512f/src/lib/mina_base/zkapp_command.ml#L803-L823
//The code reads like a placeholder, so ideally we should update this if it changes
return this.setFee(newFeePerWU.mul(new UInt64(this.transaction.accountUpdates.length + 1)))
},
setFee(newFee:UInt64) {
return toTransactionPromise(async () =>
{
self = self as Transaction<false,false>;
self.transaction.accountUpdates.forEach( au => {
au.authorization.proof = undefined;
au.authorization.signature = undefined;
au.lazyAuthorization = {kind:'lazy-signature'};
});
self.transaction.feePayer.body.fee = newFee;
self.transaction.feePayer.lazyAuthorization = {kind : 'lazy-signature'};
return self
});
},
};
return self;
}
Expand Down
Loading