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

refactor marshalActions implementation #1631

Merged
merged 9 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
119 changes: 75 additions & 44 deletions chain/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,71 +28,82 @@ var (
_ mempool.Item = (*Transaction)(nil)
)

type Transaction struct {
Base *Base `json:"base"`
type (
Actions []Action
Transaction struct {
Base *Base `json:"base"`

Actions []Action `json:"actions"`
Auth Auth `json:"auth"`
Actions Actions `json:"actions"`
Auth Auth `json:"auth"`

digest []byte
bytes []byte
size int
id ids.ID
stateKeys state.Keys
}
digest []byte
bytes []byte
size int
id ids.ID
stateKeys state.Keys
}
)
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 remove the type block and put the Actions type declaration directly above its associated functions?

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


func NewTx(base *Base, actions []Action) *Transaction {
func NewTx(base *Base, actions Actions) *Transaction {
return &Transaction{
Base: base,
Actions: actions,
}
}

// Digest returns a byte slice containing the marshaled transaction without the auth.
// method name might be misleading, as it returns the preimage of what typically
// considered to be "the message digest"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Digest returns a byte slice containing the marshaled transaction without the auth.
// method name might be misleading, as it returns the preimage of what typically
// considered to be "the message digest"
// Digest returns the byte slice representation of the unsigned tx

(optional for this PR) - would it make sense to rename this appropriately rather than documenting the fact that it's a misnomer? Looking at where we call this function, looks like we should be able to eliminate some duplicate code as well, since we call this function just to call verify on the auth that's already part of this tx in a few places.

func (t *Transaction) Digest() ([]byte, error) {
if len(t.digest) > 0 {
return t.digest, nil
}
size := t.Base.Size() + consts.Uint8Len
for _, action := range t.Actions {
actionSize, err := GetSize(action)
if err != nil {
return nil, err
}
size += consts.ByteLen + actionSize

if actionsSize, err := t.Actions.Size(); err != nil {
return nil, err
} else {
size += actionsSize
}
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 remove the if else block here, so that we have an if block for handling the error and move size += actionsSize outside the else block as it's just the expected flow

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


p := codec.NewWriter(size, consts.NetworkSizeLimit)
t.Base.Marshal(p)
p.PackByte(uint8(len(t.Actions)))
for _, action := range t.Actions {
p.PackByte(action.GetTypeID())
err := marshalInto(action, p)
if err != nil {
return nil, err
}
if err := t.marshal(p, false); err != nil {
return nil, err
}

return p.Bytes(), p.Err()
}

// Sign sign the transaction using the provided AuthFactory. The pointer receiver is not being modified by this method,
// and a new Transaction object would be returned. On success, the returned transaction is the signed transaction.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Sign sign the transaction using the provided AuthFactory. The pointer receiver is not being modified by this method,
// and a new Transaction object would be returned. On success, the returned transaction is the signed transaction.
// Sign returns a new signed transaction with the unsigned tx copied from
// the original and a signature provided by the authFactory

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

func (t *Transaction) Sign(
factory AuthFactory,
actionRegistry ActionRegistry,
authRegistry AuthRegistry,
) (*Transaction, error) {
// grab the preimage of the transaction we would want to sign.
msg, err := t.Digest()
if err != nil {
return nil, err
}
// sign the preimage
auth, err := factory.Sign(msg)
if err != nil {
return nil, err
}
t.Auth = auth
Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm - previous code added auth to the original AND created a copy w/ the signature

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 figured it would be safer to create a new object for the marshaling rather than (set auth/marshal/clear auth).
i.e. the Sign method parameters suggest it would be a readonly method, as it returns the signed transaction.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yup, much better w/ this change


// fill in the auth on the signed 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 remove the short comments here? With the added comment to Digest() I think this code is simple enough to follow w/o added comments.

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

signedTransaction := Transaction{
Base: t.Base,
Actions: t.Actions,
Auth: auth,
}
Comment on lines +88 to +92
Copy link
Collaborator

Choose a reason for hiding this comment

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

(musing) hmm so we create a signedTransaction here, but only use it to marshal and will actually never return it.

Much prefer this version where we no longer modify the original tx and return a new fully populated value. Further improvement (for a separate PR) may be to separate an unsigned and signed tx type.


// Ensure transaction is fully initialized and correct by reloading it from
// bytes
size := len(msg) + consts.ByteLen + t.Auth.Size()
size := len(msg) + consts.ByteLen + auth.Size()
p := codec.NewWriter(size, consts.NetworkSizeLimit)
if err := t.Marshal(p); err != nil {
if err := signedTransaction.Marshal(p); err != nil {
return nil, err
}
if err := p.Err(); err != nil {
Expand Down Expand Up @@ -195,7 +206,7 @@ func (t *Transaction) Units(sm StateManager, r Rules) (fees.Dimensions, error) {
// to execute a transaction.
//
// This is typically used during transaction construction.
func EstimateUnits(r Rules, actions []Action, authFactory AuthFactory) (fees.Dimensions, error) {
func EstimateUnits(r Rules, actions Actions, authFactory AuthFactory) (fees.Dimensions, error) {
var (
bandwidth = uint64(BaseSize)
stateKeysMaxChunks = []uint16{} // TODO: preallocate
Expand Down Expand Up @@ -377,25 +388,45 @@ func (t *Transaction) Marshal(p *codec.Packer) error {
p.PackFixedBytes(t.bytes)
return p.Err()
}

return t.marshalActions(p)
return t.marshal(p, true)
}

func (t *Transaction) marshalActions(p *codec.Packer) error {
func (t *Transaction) marshal(p *codec.Packer, marshalSignature bool) error {
t.Base.Marshal(p)
p.PackByte(uint8(len(t.Actions)))
for _, action := range t.Actions {
actionID := action.GetTypeID()
p.PackByte(actionID)
if err := t.Actions.marshalInto(p); err != nil {
return err
}

if marshalSignature {
authID := t.Auth.GetTypeID()
p.PackByte(authID)
t.Auth.Marshal(p)
}
return p.Err()
}

func (a Actions) Size() (int, error) {
var size int
for _, action := range a {
actionSize, err := GetSize(action)
if err != nil {
return 0, err
}
size += consts.ByteLen + actionSize
}
return size, nil
}

func (a Actions) marshalInto(p *codec.Packer) error {
p.PackByte(uint8(len(a)))
for _, action := range a {
p.PackByte(action.GetTypeID())
err := marshalInto(action, p)
if err != nil {
return err
}
}
authID := t.Auth.GetTypeID()
p.PackByte(authID)
t.Auth.Marshal(p)
return p.Err()
return nil
}

func MarshalTxs(txs []*Transaction) ([]byte, error) {
Expand Down Expand Up @@ -448,7 +479,7 @@ func UnmarshalTx(
if err != nil {
return nil, fmt.Errorf("%w: could not unmarshal base", err)
}
actions, err := unmarshalActions(p, actionRegistry)
actions, err := UnmarshalActions(p, actionRegistry)
if err != nil {
return nil, fmt.Errorf("%w: could not unmarshal actions", err)
}
Expand Down Expand Up @@ -481,15 +512,15 @@ func UnmarshalTx(
return &tx, nil
}

func unmarshalActions(
func UnmarshalActions(
p *codec.Packer,
actionRegistry *codec.TypeParser[Action],
) ([]Action, error) {
) (Actions, error) {
actionCount := p.UnpackByte()
if actionCount == 0 {
return nil, fmt.Errorf("%w: no actions", ErrInvalidObject)
}
actions := []Action{}
actions := Actions{}
for i := uint8(0); i < actionCount; i++ {
action, err := actionRegistry.Unmarshal(p)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion chain/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ func TestMarshalUnmarshal(t *testing.T) {

signedTx, err := tx.Sign(factory, actionRegistry, authRegistry)
require.NoError(err)

require.Nil(tx.Auth)
require.NotNil(signedTx.Auth)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Adding a check that we did not mutate the original tx 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.

done

require.Equal(len(signedTx.Actions), len(tx.Actions))
for i, action := range signedTx.Actions {
require.Equal(tx.Actions[i], action)
Expand Down
Loading