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

fix: transactions populated from RPC requests retain original account key order #23720

Merged
merged 18 commits into from
Apr 16, 2022

Conversation

jordaaash
Copy link
Contributor

@jordaaash jordaaash commented Mar 17, 2022

Problem

Transaction.populate is kind of broken (see #21722). It's not so much that the implementation of populate itself is broken, but rather that attempting to reserialize a transaction that was deserialized from a message is conceptually invalid.

Summary of Changes

This PR attempts to fix the issue in a surgical and slightly hacky fashion, without adding to or changing the public API, or breaking correctly behaving applications.

This is accomplished by setting an internal Message property if the Transaction was created with populate. If this Message exists, it will be used instead of recompiling the message from the transaction, which is what causes the account key order to change.

Instructions cannot be added to a populated transaction. This will throw an error at runtime. Since correctly behaving applications should not add instructions to a compiled message, I regard this as non-breaking. YMMV. If there are concerns about this, it could be limited to populated transactions with non-null signature values.

I haven't added getters/setters for feePayer, recentBlockhash, nonceInfo, or instructions. If added, these could throw errors if these are set on a populated transaction. I wanted to first propose these changes and gather feedback.

Fixes #21722
Closes #23684 (duplicate, but also adds to the public API which I don't think we should do for this)

@t-nelson
Copy link
Contributor

I think this gets the job done. Kinda sucks carrying the duplicate data around, but given the rest of the inefficiencies in the lib, probably a small price to pay for compatibility

@jstarry
Copy link
Member

jstarry commented Mar 18, 2022

Current fix looks great!

I haven't added getters/setters for feePayer, recentBlockhash, nonceInfo, or instructions. If added, these could throw errors if these are set on a populated transaction. I wanted to first propose these changes and gather feedback.

What if we cache the fields which affect message compilation (feePayer, recentBlockhash, nonceInfo, and instructions) as _populatedMessageFields and then check in compileMessage if any of them have been modified. If modifications are detected, throw an error. Otherwise return the cached _message that you added.

@jordaaash jordaaash force-pushed the web3-fix-transaction-populate branch from b3918c6 to 2bee371 Compare March 19, 2022 22:03
@jordaaash jordaaash force-pushed the web3-fix-transaction-populate branch from 2bee371 to 97158ba Compare March 19, 2022 22:09
@jordaaash
Copy link
Contributor Author

What if we cache the fields which affect message compilation (feePayer, recentBlockhash, nonceInfo, and instructions) as _populatedMessageFields and then check in compileMessage if any of them have been modified. If modifications are detected, throw an error. Otherwise return the cached _message that you added.

Hmm, we could do that. My instinctual preference is to fail fast if someone tries to modify a Transaction that shouldn't be modified rather than wait until it's serialized. But throwing errors inside of setters is kind of gross on multiple levels.

@steveluscher thoughts?

@t-nelson
Copy link
Contributor

But throwing errors inside of setters is kind of gross on multiple levels.

That was what motivated me to suggest not being able to deser into Transaction at all, but some new type that doesn't allow modifications other than addSignature

@steveluscher
Copy link
Contributor

That was what motivated me to suggest not being able to deser into Transaction at all, but some new type that doesn't allow modifications other than addSignature

I've only thought about this for three seconds, but this strikes me as a worthwhile project. You'd have to upgrade all consumers of Transaction to understand and handle this new ImmutableTransaction or CompiledTransaction type (as @t-nelson said… compatibility vs…).

If you did it this way, TypeScript will prevent you from trying to modify such a transaction, saving you from having to find out at runtime, which is nice.

More work though.

@jstarry
Copy link
Member

jstarry commented Mar 30, 2022

Cool, I think we all agree that overhauling the transaction class is the way to go but this PR is still a good incremental fix. I'm fine with it as is.

@codecov
Copy link

codecov bot commented Mar 31, 2022

Codecov Report

Merging #23720 (c81f047) into master (2da4e3e) will decrease coverage by 11.7%.
The diff coverage is n/a.

❗ Current head c81f047 differs from pull request most recent head de58333. Consider uploading reports for the commit de58333 to get more accurate results

@@             Coverage Diff             @@
##           master   #23720       +/-   ##
===========================================
- Coverage    81.8%    70.0%    -11.8%     
===========================================
  Files         581       36      -545     
  Lines      158312     2255   -156057     
  Branches        0      322      +322     
===========================================
- Hits       129518     1580   -127938     
+ Misses      28794      560    -28234     
- Partials        0      115      +115     

@jordaaash
Copy link
Contributor Author

What if we cache the fields which affect message compilation (feePayer, recentBlockhash, nonceInfo, and instructions)

@steveluscher @jstarry this has been done. Implementation is pretty ugly, but I'm not sure how to make it cleaner and still check everything.

@jordaaash jordaaash force-pushed the web3-fix-transaction-populate branch from 0ab12bf to b9d69f5 Compare March 31, 2022 03:56
@jordaaash jordaaash force-pushed the web3-fix-transaction-populate branch from b9d69f5 to e1e9c02 Compare March 31, 2022 03:57
steveluscher
steveluscher previously approved these changes Mar 31, 2022
Copy link
Contributor

@steveluscher steveluscher left a comment

Choose a reason for hiding this comment

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

LGTM! Since there are so many throws you might consider doing something like…

function fail(): never {
  throw new Error(...);
}

if (transaction.foo !== originalTransaction.foo) {
  fail();
}

…but ¯\_(ツ)_/¯

web3.js/src/transaction.ts Outdated Show resolved Hide resolved
web3.js/test/transaction.test.ts Show resolved Hide resolved
@jstarry
Copy link
Member

jstarry commented Mar 31, 2022

What if we cache the fields which affect message compilation (feePayer, recentBlockhash, nonceInfo, and instructions)

@steveluscher @jstarry this has been done. Implementation is pretty ugly, but I'm not sure how to make it cleaner and still check everything.

I think I came up with something that would be cleaner here: jordaaash#1. What do you think? (cc @steveluscher to take a look)

@steveluscher
Copy link
Contributor

I think I came up with something that would be cleaner here…

Nice; yeah, at one point I had thought about hashing properties of the transaction to be able to detect modifications, but then I forgot.

You could even use one of these ‘stable stringify’ implementations and just throw a giant bundle of properties at it. https://www.npmjs.com/search?q=stable%20stringify

Pluses: easy way to get a unique identifier
Minuses: have to carry around giant strings in memory, JSON.stringify is no picnic performance-wise

Either way, if any material property ever gets added to Transaction and we forget to add it to this check, we're back to square one. That's probably an argument to go for the solution based on two different types of transaction.

@jstarry
Copy link
Member

jstarry commented Mar 31, 2022

I believe we will deprecate this Transaction type and this hacky fix will be temporary until we migrate to a better Transaction type. I don't think this needs to be particularly performant or conscious of memory usage because a transaction itself can only be 1232 bytes of data and any stringified properties are therefore limited in size as well.

@mergify mergify bot dismissed steveluscher’s stale review April 1, 2022 17:52

Pull request has been modified.

@jordaaash
Copy link
Contributor Author

Haha that approach is so much cleaner. I merged your stringify change, though I'm not sure offhand how the objects, especially TransactionInstruction, are going to get stringified. I'll do some testing.

steveluscher
steveluscher previously approved these changes Apr 2, 2022
web3.js/src/transaction.ts Show resolved Hide resolved
@steveluscher
Copy link
Contributor

If this need to stringify objects in a way that's stable – regardless of whether keys are in different orders – ever comes up again, this is one of my favs.

https://github.com/facebook/relay/blob/ec03bf455d28b7eb30e5251731bf768eac2c2abd/packages/relay-runtime/store/OperationExecutor.js#L1668-L1670

jstarry
jstarry previously approved these changes Apr 2, 2022
@stale
Copy link

stale bot commented Apr 16, 2022

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale [bot only] Added to stale content; results in auto-close after a week. label Apr 16, 2022
@jstarry
Copy link
Member

jstarry commented Apr 16, 2022

@Mergifyio rebase

@stale stale bot removed the stale [bot only] Added to stale content; results in auto-close after a week. label Apr 16, 2022
@mergify
Copy link
Contributor

mergify bot commented Apr 16, 2022

rebase

❌ Base branch update has failed

Git reported the following error:

Rebasing (1/13)
Rebasing (2/13)
Rebasing (3/13)
Rebasing (4/13)
Rebasing (5/13)
Rebasing (6/13)
Auto-merging core/src/accounts_hash_verifier.rs
CONFLICT (content): Merge conflict in core/src/accounts_hash_verifier.rs
Auto-merging runtime/src/accounts_db.rs
CONFLICT (content): Merge conflict in runtime/src/accounts_db.rs
Auto-merging runtime/src/accounts_hash.rs
CONFLICT (content): Merge conflict in runtime/src/accounts_hash.rs
error: could not apply a6e703691... add hash calc config.use_write_cache (#24005)
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply a6e703691... add hash calc config.use_write_cache (#24005)

err-code: 8C36A

@jstarry jstarry changed the title web3: fix transaction populate fix: transactions populated from RPC requests retain original account key order Apr 16, 2022
@jstarry
Copy link
Member

jstarry commented Apr 16, 2022

@jordansexton do you mind rebasing and merging this when you get the chance?

@mergify mergify bot dismissed stale reviews from jstarry and steveluscher April 16, 2022 19:03

Pull request has been modified.

@jordaaash jordaaash force-pushed the web3-fix-transaction-populate branch from 24cbbba to c81f047 Compare April 16, 2022 19:22
@jordaaash
Copy link
Contributor Author

jordaaash commented Apr 16, 2022

@jstarry unfortunately there's a failing test which surfaced this issue:

transaction = Transaction.populate(transactionOrMessage);
}
if (transaction.nonceInfo && signers) {
transaction.sign(...signers);
} else {
let disableCache = this._disableBlockhashCaching;
for (;;) {
transaction.recentBlockhash = await this._recentBlockhash(disableCache);

Transaction simulation from a Message relies on modifying the transaction after populating it. If I merge this change as is, it will break transaction simulation.

I did something very hacky and unset the internal properties in this instance to keep the current behavior of simulateTransaction:

de58333

If tests pass now, I'll merge away.

@jordaaash jordaaash force-pushed the web3-fix-transaction-populate branch from c81f047 to de58333 Compare April 16, 2022 19:28
@jordaaash jordaaash merged commit 21dacef into solana-labs:master Apr 16, 2022
@jordaaash jordaaash deleted the web3-fix-transaction-populate branch April 16, 2022 19:29
jeffwashington added a commit to jeffwashington/solana that referenced this pull request Jun 29, 2022
… key order (solana-labs#23720)

* fix: transaction populate

* chore: web3: fix tx serialization test

* chore: web3: run prettier

* fix: web3: transaction populate

* fix: web3: handle nonce info

* add hash calc config.use_write_cache (solana-labs#24005)

* restore existing overlapping overflow (solana-labs#24010)

* Stringify populated transaction fields

* fix: web3: compare stringified JSON

* chore: web3: remove eslint indent rule that conflicts with prettier

* fix: web3: explicitly call toJSON

* fix: web3: add test for compileMessage

* fix: web3: make JSON internal

* fix: web3: connection simulation from message relies on mutating transaction

Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com>
Co-authored-by: Jack May <jack@solana.com>
Co-authored-by: Justin Starry <justin@solana.com>
jeffwashington added a commit to jeffwashington/solana that referenced this pull request Jun 30, 2022
… key order (solana-labs#23720)

* fix: transaction populate

* chore: web3: fix tx serialization test

* chore: web3: run prettier

* fix: web3: transaction populate

* fix: web3: handle nonce info

* add hash calc config.use_write_cache (solana-labs#24005)

* restore existing overlapping overflow (solana-labs#24010)

* Stringify populated transaction fields

* fix: web3: compare stringified JSON

* chore: web3: remove eslint indent rule that conflicts with prettier

* fix: web3: explicitly call toJSON

* fix: web3: add test for compileMessage

* fix: web3: make JSON internal

* fix: web3: connection simulation from message relies on mutating transaction

Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com>
Co-authored-by: Jack May <jack@solana.com>
Co-authored-by: Justin Starry <justin@solana.com>
jeffwashington added a commit to jeffwashington/solana that referenced this pull request Jun 30, 2022
… key order (solana-labs#23720)

* fix: transaction populate

* chore: web3: fix tx serialization test

* chore: web3: run prettier

* fix: web3: transaction populate

* fix: web3: handle nonce info

* add hash calc config.use_write_cache (solana-labs#24005)

* restore existing overlapping overflow (solana-labs#24010)

* Stringify populated transaction fields

* fix: web3: compare stringified JSON

* chore: web3: remove eslint indent rule that conflicts with prettier

* fix: web3: explicitly call toJSON

* fix: web3: add test for compileMessage

* fix: web3: make JSON internal

* fix: web3: connection simulation from message relies on mutating transaction

Co-authored-by: Jeff Washington (jwash) <wash678@gmail.com>
Co-authored-by: Jack May <jack@solana.com>
Co-authored-by: Justin Starry <justin@solana.com>
kewde added a commit to ExodusMovement/solana-web3.js that referenced this pull request Jan 11, 2023
kewde added a commit to ExodusMovement/solana-web3.js that referenced this pull request Jan 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants