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

Break the unsigned transaction data out of the transaction #1640

Merged
merged 13 commits into from
Oct 11, 2024

Conversation

tsachiherman
Copy link
Contributor

@tsachiherman tsachiherman commented Oct 8, 2024

What ?

Break the existing Transaction struct into Transaction and an embedded struct TransactionData

Why ?

The usage of Transaction might not be sufficient to distinguish between the sign and the unsigned transaction.
The unsigned transaction ( i.e. TransactionData ) should be used only in the case of a new transaction being created, and before it's being signed.
The signed transaction should be used everywhere else. For that reason, the Transaction doesn't come with any constructor of it's own ( only marshaler/unmarshaler ).

@tsachiherman tsachiherman self-assigned this Oct 8, 2024
@tsachiherman tsachiherman marked this pull request as ready for review October 9, 2024 13:09
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's actually easier to review this file if the struct definition is kept in transaction.go because so it shows up as a rename instead of a delete + add

stateKeys state.Keys
}

func (t *SignedTransaction) UnsignedTxnBytes() []byte { return t.Transaction.bytes }
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. nit: I prefer the receiver name being s since it's the first letter of the struct name.
  2. Seems like we don't need this function since you can just call signedTx.UnsignedBytes() because Transaction is an embedded type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Regarding the second one, the main advantage in having the UnsignedTxnBytes in SignedTransaction is that it doesn't need to return a potential error. The error case was already handled during the unmarshaler, so no error handling is required by the caller.

if len(t.unsignedBytes) > 0 {
return t.unsignedBytes, nil
// Bytes returns the byte slice representation of the tx
func (t *Transaction) Bytes() ([]byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems to me like the diff in this function isn't needed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

t.Base.Marshal(p)
if err := t.Actions.marshalInto(p); err != nil {
if err := p.Err(); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this diff isn't needed since Packer will stop at the first error (ref).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see.. so you're saying that adding this would have no practical functional impact since the packet would effectively be "blocked" for any new additions due to the error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

_ mempool.Item = (*SignedTransaction)(nil)
)

type SignedTransaction struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure how I feel about this naming. I personally get pretty confused seeing SignedTransaction and Transaction as I keep assuming Transaction is signed. We could consider something else like SignedTransaction and UnsignedTransaction or Transaction and TransactionData.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like we have only several places the use the unsigned transaction. Therefore, I'm not really opinionated here. I've changed the following:
SignedTransaction -> Transaction
Transaction -> TransactionData

@tsachiherman tsachiherman enabled auto-merge (squash) October 10, 2024 13:48
@tsachiherman tsachiherman changed the title Create SignedTransaction out of the existing Transaction Break the unsigned transaction data out of the transaction Oct 10, 2024
Copy link
Collaborator

@aaronbuchwald aaronbuchwald left a comment

Choose a reason for hiding this comment

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

Few small nits and then this LGTM - thanks for cleaning this up, this makes it way easier to reason about the fields of a transaction that have been populated

@@ -170,8 +170,8 @@ func (cli *JSONRPCClient) GenerateTransactionManual(

// Build transaction
actionCodec, authCodec := parser.ActionCodec(), parser.AuthCodec()
tx := chain.NewTx(base, actions)
tx, err := tx.Sign(authFactory, actionCodec, authCodec)
unsignedTx := chain.NewTxnData(base, actions)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we change to NewTxData ? We typically use tx instead of txn

@@ -360,9 +360,12 @@ var _ = ginkgo.Describe("[Tx Processing]", ginkgo.Serial, func() {
require.NoError(err)
auth, err := authFactory.Sign(unsignedTxBytes)
require.NoError(err)
tx.Auth = auth
signedTxn := chain.Transaction{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we use signedTx instead of signedTxn ?

vm/vm.go Outdated
continue
}
if err := tx.Auth.Verify(ctx, unsignedTxBytes); err != nil {
if err := tx.Verify(ctx); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we update the function name to VerifyAuth so that it's explicit what's being verified vs. Execute and PreExecute ?

Comment on lines 141 to 143
// call Bytes so that the "bytes" field would get populated.
_, err = txBeforeSign.UnsignedBytes()
require.NoError(err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

(not necessary for this PR) would it make sense to always create TransactionData via a single constructor, so that it's clear all of the fields are populated in one place?

@@ -28,28 +28,23 @@ var (
_ mempool.Item = (*Transaction)(nil)
)

type Transaction struct {
type TransactionData struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

❤️ makes this way easier to review


func (t *Transaction) ID() ids.ID { return t.id }
func (s *Transaction) Bytes() []byte { return s.bytes }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we change from using (s *Transaction) to (t *Transaction) to follow the convention of using the lowercase first letter of the type?

@tsachiherman tsachiherman merged commit bca7739 into main Oct 11, 2024
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants