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

feat: ETH compatibility in Filecoin : Support Homestead and EIP-155 Ethereum transactions("legacy" transactions) in Filecoin #11969

Open
wants to merge 28 commits into
base: master
Choose a base branch
from

Conversation

aarshkshah1992
Copy link
Contributor

@aarshkshah1992 aarshkshah1992 commented May 7, 2024

Related Issues

For #11859

Proposed Changes

FIP discussion at filecoin-project/FIPs#962.
FIP proposal at filecoin-project/FIPs#995

Implement Ethereum Homestead transactions and EIP-155 transactions(both are referred to as "legacy" transactions) in Filecoin.

Additional Info

Checklist

Before you mark the PR ready for review, please make sure that:

  • Commits have a clear commit message.
  • PR title is in the form of of <PR type>: <area>: <change being made>
    • example: fix: mempool: Introduce a cache for valid signatures
    • PR type: fix, feat, build, chore, ci, docs, perf, refactor, revert, style, test
    • area, e.g. api, chain, state, market, mempool, multisig, networking, paych, proving, sealing, wallet, deps
  • If the PR affects users (e.g., new feature, bug fix, system requirements change), update the CHANGELOG.md and add details to the UNRELEASED section.
  • New features have usage guidelines and / or documentation updates in
  • Tests exist for new functionality or change in behavior
  • CI is green

@aarshkshah1992 aarshkshah1992 changed the title Add support for legacy Homestead transactions to Filecoin feat: ETH compatibility in Filecoin : Support legacy Homestead Ethereum transactions in Filecoin May 7, 2024
@rjan90 rjan90 linked an issue May 9, 2024 that may be closed by this pull request
…ctions in Filecoin (#11970)

* itests passing for 155 tx

* first working version for EIP-155 transactions

* green itest

* add docs

* tests

* remove print stmt

* remove print stmt

* validate signature

* changes as per zen's review

* correct signature verification
@aarshkshah1992 aarshkshah1992 changed the title feat: ETH compatibility in Filecoin : Support legacy Homestead Ethereum transactions in Filecoin feat: ETH compatibility in Filecoin : Support Homestead and EIP-155 Ethereum transactions("legacy" transactions) in Filecoin May 13, 2024
@aarshkshah1992 aarshkshah1992 changed the base branch from integration/eth-legacy-tx to master May 13, 2024 06:19
Copy link
Contributor

@arajasek arajasek left a comment

Choose a reason for hiding this comment

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

Some notes:

  • The new logic for the two types of Homestead transactions seems correct, assuming the FIP is correctly specified. I'll stare at it more in second review, but everything seems to do what it should.
  • I'm not too strongly in favour of this change, it's adding a lot more edgecase-handling to what is already one of the gnarliest parts of the Filecoin protocol. I see the strong support for it in the FIP discussion, and this is about as lightweight an approach as can be expected for this change, so I'm happy to disagree-and-commit. But I do think this is a step in the wrong direction.
    • In particular, I don't think we should lean into an attitude of "well, it's just in the client, so it's okay". It's still very much sharp edges in the protocol, and the fact that it's in the client only means that it needs to be (potentially incorrectly / incompatibly) re-implemented by Forest and Venus too.
  • Directly related to the previous point, I unfortunately don't think this PR is safe to land as-is, because I strongly suspect it unintentionally breaks existing behaviour in subtle ways. In general, it seems like this PR will tighten conditions on Eth1559 transactions. I don't think there's anything wrong with the changes, but they'll still break consensus on nv22 (so, if anything, we'll have to put them behind a network version check).
    • As an example (the only one I can see so far, though there might be more), I'm pretty sure we can have messages with ID addresses as the sender carrying a Delegated signature today. I think that behaviour will change with this PR (it'll become invalid).

to = &addr
} else {
return nil, nil,
fmt.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can also be CreateExternal

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

// For EthLegacy155TxArgs, the digest that is used to create a signed transaction includes the `ChainID` but the serialised RLP transaction
// does not include the `ChainID` as an explicit field. Instead, the `ChainID` is included in the V value of the signature as mentioned above.
type EthLegacy155TxArgs struct {
LegacyTx *EthLegacyHomesteadTxArgs
Copy link
Contributor

Choose a reason for hiding this comment

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

As written, this is a guarantee to cause bugs in the future when people accidentally call the "inner" methods on EthLegacyHomesteadTxArgs instead of the correct methods (on EthLegacy155TxArgs). Please rewrite this, you could:

  • create an inner struct that's just the fields (no logic), and then have two separate structs that contain that struct
  • just duplicate the fields in EthLegacy155TxArgs, it's fine to do so
  • if you really want to keep the existing pattern, make LegacyTx private, and rewrite external usages of it to access it properly (eg. by creating a constructor)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed this by making it private.

@ZenGround0
Copy link
Contributor

But I do think this is a step in the wrong direction.

When you say this are you referring to the FIP change itself?

As an example (the only one I can see so far, though there might be more), I'm pretty sure we can have messages with ID addresses as the sender carrying a Delegated signature today. I think that behaviour will change with this PR (it'll become invalid).

Can you explain this more. My understanding is that EIP1559 Txs won't have any behavior changes at all with this change. How does this PR/FIP impact ID address sender of EIP1559 Tx?

txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(&msg.Message)
signatureCopy.Data = make([]byte, len(msg.Signature.Data))
copy(signatureCopy.Data, msg.Signature.Data)
ethTx, err := ethtypes.EthTransactionFromSignedFilecoinMessage(msg)
Copy link
Contributor

Choose a reason for hiding this comment

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

Coming back to this again I'm having some paranoid thoughts. Does this PR open up forking as written?
We release this code before nv23, someone comes in with a legacy Tx, sends it over to an SP with updated software. That SP includes in a block. Now an SP who is not updated yet reads the block. It has this weird signature with one extra byte. It doesn't validate per its code. SP rejects block.

I could be off base and missing some subtlty that prevents this. Just want to think it over with you before merge.

Copy link
Contributor

@snissn snissn May 22, 2024

Choose a reason for hiding this comment

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

I agree with @arajasek and @ZenGround0 that this PR would definitely create consensus issues if some nodes are able to execute a transaction and other nodes would reject it. Was there any discussion in the FIP process around how to handle the consensus breaking aspects of supporting this new transaction 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.

@snissn @ZenGround0 I have update this PR to only accept legacy txs if the node is on NV23. If the Node still hasn't upgraded to NV23, it will fail to accept legacy txns.

I have also added an itest for it. It's in test TestLegacyEIP155ValueTransferValidSignatureFailsNV22. I think this should suffice. Let me know if you have any other concerns.

Copy link

github-actions bot commented May 23, 2024

@aarshkshah1992
Copy link
Contributor Author

@arajasek @ZenGround0

  • I agree that this FIP needs to be implemented by both Forrest and Venus but that should not be a reason to not ship a protocol change that has been requested by multiple users. We need to build for what they need.

- I have added network version gating to this and so nodes will not accept legacy txs if they are not on NV23.

In general, it seems like this PR will tighten conditions on Eth1559 transactions. I don't think there's anything wrong with the changes, but they'll still break consensus on nv22 (so, if anything, we'll have to put them behind a network version check).
As an example (the only one I can see so far, though there might be more), I'm pretty sure we can have messages with ID addresses as the sender carrying a Delegated signature today. I think that behaviour will change with this PR (it'll become invalid).

This is the part that I do not understand. Why will messages with ID addresses and a delegated signature become invalid with this PR ? I

Comment on lines +47 to +75
// SignLegacyHomesteadTransaction signs a legacy Homstead Ethereum transaction in place with the supplied private key.
func (e *EVM) SignLegacyEIP155Transaction(tx *ethtypes.EthLegacy155TxArgs, privKey []byte, chainID big.Int) {
preimage, err := tx.ToRlpUnsignedMsg()
require.NoError(e.t, err)

// sign the RLP payload
signature, err := sigs.Sign(crypto.SigTypeDelegated, privKey, preimage)
require.NoError(e.t, err)

signature.Data = append([]byte{ethtypes.EthLegacy155TxSignaturePrefix}, signature.Data...)

chainIdMul := big.Mul(chainID, big.NewInt(2))
vVal := big.Add(chainIdMul, big.NewIntUnsigned(35))

switch signature.Data[len(signature.Data)-1] {
case 0:
vVal = big.Add(vVal, big.NewInt(0))
case 1:
vVal = big.Add(vVal, big.NewInt(1))
}

signature.Data = append(signature.Data[:65], vVal.Int.Bytes()...)

err = tx.InitialiseSignature(*signature)
require.NoError(e.t, err)
}

// SignLegacyHomesteadTransaction signs a legacy Homstead Ethereum transaction in place with the supplied private key.
func (e *EVM) SignLegacyHomesteadTransaction(tx *ethtypes.EthLegacyHomesteadTxArgs, privKey []byte) {
Copy link
Member

@raulk raulk May 23, 2024

Choose a reason for hiding this comment

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

Can we try to get rid of these specialized methods? Some ideas:

  1. Make SignTransaction take an interface and the chain id:

    interface RlpUnsignedMsger {
        ToRlpUnsignedMsg() ([]byte, error)
    }
  2. Encapsulate the signature enhancement in a standard method in the tx type itself.

@aarshkshah1992
Copy link
Contributor Author

So tested this with CB Wallet on devnet after implementing Network Version Gating and looks good now. Transactions fail for NV22(when devnet epoch < NV23 epoch) but work for NV23(when devnet epoch >= NV23 epoch) !

Screenshot 2024-05-23 at 6 22 37 PM Screenshot 2024-05-23 at 6 32 12 PM

@@ -824,7 +824,16 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error {
return nil
}

if err := consensus.AuthenticateMessage(m, m.Message.From); err != nil {
mp.curTsLk.RLock()
Copy link
Contributor

Choose a reason for hiding this comment

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

from a thread safety perspective this block looks ok to me

Copy link
Member

Choose a reason for hiding this comment

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

Are you sure we want to do this here? We previously did this here:

// TODO: I'm not thrilled about depending on filcns here, but I prefer this to duplicating logic
if !consensus.IsValidForSending(nv, senderAct) {
return false, xerrors.Errorf("sender actor %s is not a valid top-level sender", m.Message.From)
}

We intentionally avoid taking any locks or looking at any global state in checkMsg or anything it calls.

@ZenGround0 ZenGround0 self-requested a review May 23, 2024 22:00
@arajasek
Copy link
Contributor

But I do think this is a step in the wrong direction.

When you say this are you referring to the FIP change itself?

Yes.

As an example (the only one I can see so far, though there might be more), I'm pretty sure we can have messages with ID addresses as the sender carrying a Delegated signature today. I think that behaviour will change with this PR (it'll become invalid).

Can you explain this more. My understanding is that EIP1559 Txs won't have any behavior changes at all with this change. How does this PR/FIP impact ID address sender of EIP1559 Tx?

EIP-1559 Txs shouldn't have any behaviour changes with this FIP. I'm concerned that this PR will cause some changes (example above) due to the refactor, which is consensus-breaking.

}
roundTripMsg, err := txArgs.ToUnsignedMessage(msg.Message.From)

if ethTx.Type() != ethtypes.EIP1559TxType {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: use && instead of nested ifs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this.

@arajasek
Copy link
Contributor

@arajasek @ZenGround0

* I agree that this FIP needs to be implemented by both Forrest and Venus but that should not be a reason to not ship a protocol change that has been requested by multiple users. We need to build for what they need.

The objection isn't that 3 implementations have to ship the change, the objection is that this is further messing up what is already a pretty janky part of the Filecoin protocol.

This is the part that I do not understand. Why will messages with ID addresses and a delegated signature become invalid with this PR ? I

Because of the refactor. You're now calling EthTransactionFromSignedFilecoinMessage instead of EthTxArgsFromUnsignedEthMessage as part of signature authentication, which is asserting that the sender is an f4 address.

@@ -824,7 +824,16 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error {
return nil
}

if err := consensus.AuthenticateMessage(m, m.Message.From); err != nil {
mp.curTsLk.RLock()
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure we want to do this here? We previously did this here:

// TODO: I'm not thrilled about depending on filcns here, but I prefer this to duplicating logic
if !consensus.IsValidForSending(nv, senderAct) {
return false, xerrors.Errorf("sender actor %s is not a valid top-level sender", m.Message.From)
}

We intentionally avoid taking any locks or looking at any global state in checkMsg or anything it calls.

go.mod Outdated Show resolved Hide resolved
@@ -16,36 +16,51 @@ import (
// SignedMessage was signed by the indicated Address, computing the correct
// signature payload depending on the signature type. The supplied Address type
// must be recognized by the registered verifier for the signature type.
func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error {
func AuthenticateMessage(msg *types.SignedMessage, signer address.Address, nv network.Version) error {
Copy link
Member

Choose a reason for hiding this comment

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

We previously enforced network-version specific requirements here:

if !IsValidForSending(nv, act) {
return xerrors.New("Sender must be an account actor")
}

I'd avoid network version logic here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Stebalien I have addressed this.

@aarshkshah1992
Copy link
Contributor Author

aarshkshah1992 commented May 27, 2024

@arajasek

This is the part that I do not understand. Why will messages with ID addresses and a delegated signature become invalid with this PR ? I

Because of the refactor. You're now calling EthTransactionFromSignedFilecoinMessage instead of EthTxArgsFromUnsignedEthMessage as part of signature authentication, which is asserting that the sender is an f4 address.

The same behaviour is also enforced in master for EIP-1559 transactions. There is no behaviour change for EIP-1559 txns in this PR.
See

if !IsEthAddress(smsg.Message.From) {
in master.

@aarshkshah1992
Copy link
Contributor Author

@Stebalien Pls can you approve this if lgty ?

@arajasek
Copy link
Contributor

@arajasek

Because of the refactor. You're now calling EthTransactionFromSignedFilecoinMessage instead of EthTxArgsFromUnsignedEthMessage as part of signature authentication, which is asserting that the sender is an f4 address.

The same behaviour is also enforced in master for EIP-1559 transactions. There is no behaviour change for EIP-1559 txns in this PR. See

if !IsEthAddress(smsg.Message.From) {

in master.

Please read the code and my message more carefully. We are calling EthTxArgsFromUnsignedEthMessage on master, which does not assert that the sender is an f4 address. AFAIC tell nothing currently running on mainnet does.

With this refactor, we will start asserting that the sender of EIP-1559 transactions is an f4 address. If I'm right about all of this, that would be a consensus break. I may not be right, but we need to be sure of that, and this PR cannot land until we are.

@aarshkshah1992
Copy link
Contributor Author

aarshkshah1992 commented May 27, 2024

Oh hey @arajasek

I see what you mean. We do NOT in fact allow EIP-1559 messages where sender is a non-ETH(f4) address and fail these transactions during signature verification.

See:

maybeaddr, err := address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, addrHash[12:])

Basically, the sender of a delegated signature type MUST be an f4 address for the signature verification to succeed.

@arajasek
Copy link
Contributor

Oh hey @arajasek

I see what you mean. We do NOT in fact allow EIP-1559 messages where sender is a non-ETH(f4) address and fail these transactions during signature verification.

See:

maybeaddr, err := address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, addrHash[12:])

Basically, the sender of a delegated signature type MUST be an f4 address for the signature verification to succeed.

@aarshkshah1992 Thanks for digging further! I’m not quite convinced yet -- the line you linked to extracts the address from the signature carried by the message (which can obviously never be an ID address). But what happens if the sender field of a message is an ID address? That case will definitely not be supported (invalid message) after this refactor, because of the additional checks in EthTransactionFromSignedFilecoinMessage. But is that possible on mainnet today? If so, this refactor is unsafe.

@aarshkshah1992
Copy link
Contributor Author

aarshkshah1992 commented May 29, 2024

Oh hey @arajasek

  • The line immediately below that at

    if maybeaddr != a {
    ensures that the address passed down to the Verify function matches the address extracted from the signature and the address extracted from the signature has to be an ID address as per
    maybeaddr, err := address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, addrHash[12:])

  • You can see that the sender of the message is indeed passed as an argument to the signature Verify function at

    if err := sigs.Verify(&msg.Signature, signer, digest); err != nil {
    .

@arajasek
Copy link
Contributor

The line immediately below that at

if maybeaddr != a {

ensures that the address passed down to the Verify function matches the address extracted from the signature and the address extracted from the signature has to be an ID address as per
maybeaddr, err := address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, addrHash[12:])

I assume you mean has to be a delegated (f4) address here?

You can see that the sender of the message is indeed passed as an argument to the signature Verify function at

if err := sigs.Verify(&msg.Signature, signer, digest); err != nil {

Is the sender of the message being passed? I see the signer being passed as an argument here, but going up one more level it looks like that has already been resolved to a deterministic address. AFAIC tell this confirms my concern?

@arajasek
Copy link
Contributor

To break this down:

  • Using master today, can I send a message carrying an f4 signature on mainnet whose From field is an ID address?
    • Yes / No / I don't know.
  • With this refactor, can I send a(n EIP-1559) message carrying an f4 signature on mainnet whose From field is an ID address?
    • Yes / No / I don't know.

If the answer to either question is I don't know, we need to figure out the actual answer (with confidence). If the answer to both questions isn't the same (both Yes or both No), we have a consensus split caused by this change, and so it isn't safe to land.

(I think the answers are Yes and No respectively, which would be a problem if true, but that's what needs to be confirmed).

@@ -113,10 +113,6 @@ func EthTransactionFromSignedFilecoinMessage(smsg *types.SignedMessage) (EthTran
if smsg == nil {
return nil, errors.New("signed message is nil")
}
// Validate the sender's address format.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mpool validation/Block validation already enforces that the sender should either be an f4 address or an ID address that can be resolved to an f4 address. No harm in allowing legacy txns to have ID addresses as sender.

@aarshkshah1992
Copy link
Contributor Author

aarshkshah1992 commented May 30, 2024

@arajasek

  • So based on feat: ETH compatibility in Filecoin : Support Homestead and EIP-155 Ethereum transactions("legacy" transactions) in Filecoin #11969 (comment), I wrote some tests on master
  • The test is at [DO NOT MERGE] EIP-1559 transaction with the Sender as an ID address fails #12061 (has some new code in mpool validation to get it to work)
  • You can't get EIP-1559 messages with non-ID addresses through the Lotus mpool verification implementation -> it has to have an f4 address for mpool inclusion
  • However, if an incoming block has an EIP-1559 transaction with the sender as an ID address (albeit one that can be resolved to a f4 address) -> it will go through and you are right here 👍
  • So, if a Miner decides to mine a block using their own custom mpool implementation that does not use/copy the Lotus mpool implementation -> EIP-1559 transactions with ID senders will indeed go through on master but fail on our branch with the legacy TX implementation (tried that as well)
  • So yeah, this is indeed a consensus breaking change for the protocol 👍
  • I've fixed this in the latest commit on the PR -> basically have completely removed the "sender should be an f4 address" check. If the sender is an ID address that can not be resolved to an f4 address, signature verification will anyways fail. And we want to keep the legacy tx behaviour with the EIP-1559 tx behaviour.

@arajasek
Copy link
Contributor

Thanks a lot for looking into this in detail! I'll give it a second review today, but it should be good to go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Review
Development

Successfully merging this pull request may close these issues.

Add eth legacy tx
6 participants