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

Account abstraction #9831

Merged
merged 4 commits into from
Dec 16, 2022
Merged
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
5 changes: 5 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,11 @@ workflows:
suite: itest-dup_mpool_messages
target: "./itests/dup_mpool_messages_test.go"

- test:
name: test-itest-eth_account_abstraction
suite: itest-eth_account_abstraction
target: "./itests/eth_account_abstraction_test.go"

- test:
name: test-itest-fevm
suite: itest-fevm
Expand Down
18 changes: 18 additions & 0 deletions chain/actors/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,24 @@ func IsPaymentChannelActor(c cid.Cid) bool {
return false
}

func IsEmbryoActor(c cid.Cid) bool {
name, _, ok := actors.GetActorMetaByCode(c)
if ok {
return name == "embryo"
}

return false
}

func IsEthAccountActor(c cid.Cid) bool {
name, _, ok := actors.GetActorMetaByCode(c)
if ok {
return name == "ethaccount"
}

return false
}

func makeAddress(addr string) address.Address {
ret, err := address.NewFromString(addr)
if err != nil {
Expand Down
18 changes: 18 additions & 0 deletions chain/actors/builtin/builtin.go.template
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,24 @@ func IsPaymentChannelActor(c cid.Cid) bool {
return false
}

func IsEmbryoActor(c cid.Cid) bool {
name, _, ok := actors.GetActorMetaByCode(c)
if ok {
return name == "embryo"
}

return false
}

func IsEthAccountActor(c cid.Cid) bool {
name, _, ok := actors.GetActorMetaByCode(c)
if ok {
return name == "ethaccount"
}

return false
}

func makeAddress(addr string) address.Address {
ret, err := address.NewFromString(addr)
if err != nil {
Expand Down
12 changes: 0 additions & 12 deletions chain/actors/builtin/embryo.go

This file was deleted.

10 changes: 5 additions & 5 deletions chain/consensus/filcns/filecoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,15 +436,15 @@ func (filec *FilecoinEC) VerifyWinningPoStProof(ctx context.Context, nv network.
return nil
}

func isValidForSending(act *types.Actor) bool {
if builtin.IsAccountActor(act.Code) {
func IsValidForSending(act *types.Actor) bool {
if builtin.IsAccountActor(act.Code) || builtin.IsEthAccountActor(act.Code) {
return true
}

// HACK: Allow Eth embryos to send messages
if !builtin.IsEmbryo(act.Code) || act.Address == nil || act.Address.Protocol() != address.Delegated {
if !builtin.IsEmbryoActor(act.Code) || act.Address == nil || act.Address.Protocol() != address.Delegated {
return false
}

id, _, err := varint.FromUvarint(act.Address.Payload())
return err == nil && id == builtintypes.EthereumAddressManagerActorID
}
Expand Down Expand Up @@ -521,7 +521,7 @@ func (filec *FilecoinEC) checkBlockMessages(ctx context.Context, b *types.FullBl
return xerrors.Errorf("failed to get actor: %w", err)
}

if !isValidForSending(act) {
if !IsValidForSending(act) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to put this behind a version check? I guess the actor couldn't exist on-chain... but I wanted to double check.

Copy link
Contributor Author

@arajasek arajasek Dec 15, 2022

Choose a reason for hiding this comment

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

Yeah, we shouldn't need to as written.

return xerrors.New("Sender must be an account actor")
}
nonces[sender] = act.Nonce
Expand Down
19 changes: 15 additions & 4 deletions chain/messagepool/messagepool.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (

"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/consensus/filcns"
"github.com/filecoin-project/lotus/chain/eth"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
Expand Down Expand Up @@ -806,7 +807,7 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error {
if m.Signature.Type == crypto.SigTypeDelegated {
txArgs, err := eth.NewEthTxArgsFromMessage(&m.Message)
if err != nil {
return err
return xerrors.Errorf("failed to convert to eth tx args: %w", err)
}
msg, err := txArgs.OriginalRlpMsg()
if err != nil {
Expand Down Expand Up @@ -873,18 +874,28 @@ func (mp *MessagePool) addTs(ctx context.Context, m *types.SignedMessage, curTs
mp.lk.Lock()
defer mp.lk.Unlock()

senderAct, err := mp.api.GetActorAfter(m.Message.From, curTs)
if err != nil {
return false, xerrors.Errorf("failed to get sender actor: %w", err)
}

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

publish, err := mp.verifyMsgBeforeAdd(ctx, m, curTs, local)
if err != nil {
return false, err
return false, xerrors.Errorf("verify msg failed: %w", err)
}

if err := mp.checkBalance(ctx, m, curTs); err != nil {
return false, err
return false, xerrors.Errorf("failed to check balance: %w", err)
}

err = mp.addLocked(ctx, m, !local, untrusted)
if err != nil {
return false, err
return false, xerrors.Errorf("failed to add locked: %w", err)
}

if local {
Expand Down
2 changes: 1 addition & 1 deletion chain/messagepool/messagepool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func (tma *testMpoolAPI) GetActorAfter(addr address.Address, ts *types.TipSet) (
}

return &types.Actor{
Code: builtin2.StorageMarketActorCodeID,
Code: builtin2.AccountActorCodeID,
Nonce: nonce,
Balance: balance,
}, nil
Expand Down
11 changes: 8 additions & 3 deletions chain/types/ethtypes/eth_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,15 @@ func TryEthAddressFromFilecoinAddress(addr address.Address, allowId bool) (EthAd

func EthAddressFromFilecoinAddress(addr address.Address) (EthAddress, error) {
ethAddr, ok, err := TryEthAddressFromFilecoinAddress(addr, true)
if !ok && err == nil {
err = xerrors.Errorf("failed to convert filecoin address %s to an equivalent eth address", addr)
if err != nil {
return EthAddress{}, xerrors.Errorf("failed to try converting filecoin to eth addr: %w", err)
}

if !ok {
return EthAddress{}, xerrors.Errorf("failed to convert filecoin address %s to an equivalent eth address", addr)
}
return ethAddr, err

return ethAddr, nil
}

func EthAddressFromHex(s string) (EthAddress, error) {
Expand Down
2 changes: 1 addition & 1 deletion chain/vm/invoker.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func DecodeParams(b []byte, out interface{}) error {

func DumpActorState(i *ActorRegistry, act *types.Actor, b []byte) (interface{}, error) {
// Account & Embryo code special case
if builtin.IsAccountActor(act.Code) || builtin.IsEmbryo(act.Code) {
if builtin.IsAccountActor(act.Code) || builtin.IsEmbryoActor(act.Code) {
return nil, nil
}

Expand Down
127 changes: 127 additions & 0 deletions itests/eth_account_abstraction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package itests

import (
"context"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/exitcode"

"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/lotus/chain/eth"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/wallet/key"
"github.com/filecoin-project/lotus/itests/kit"
)

// TestEthAccountAbstraction goes over the account abstraction workflow:
// - an embryo is created when it receives a message
// - the embryo turns into an EOA when it sends a message
func TestEthAccountAbstraction(t *testing.T) {
kit.QuietMiningLogs()

blockTime := 100 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
ens.InterconnectAll().BeginMining(blockTime)

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

secpKey, err := key.GenerateKey(types.KTDelegated)
require.NoError(t, err)

embryoAddress, err := client.WalletImport(ctx, &secpKey.KeyInfo)
require.NoError(t, err)

fmt.Println(embryoAddress)

// create an embryo actor at the target address
msgCreateEmbryo := &types.Message{
From: client.DefaultKey.Address,
To: embryoAddress,
Value: abi.TokenAmount(types.MustParseFIL("100")),
}
smCreateEmbryo, err := client.MpoolPushMessage(ctx, msgCreateEmbryo, nil)
require.NoError(t, err)
mLookup, err := client.StateWaitMsg(ctx, smCreateEmbryo.Cid(), 3, api.LookbackNoLimit, true)
require.NoError(t, err)
require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode)

// confirm the embryo is an embryo
embryoActor, err := client.StateGetActor(ctx, embryoAddress, types.EmptyTSK)
require.NoError(t, err)

require.True(t, builtin.IsEmbryoActor(embryoActor.Code))

// send a message from the embryo address
msgFromEmbryo := &types.Message{
From: embryoAddress,
// self-send because an "eth tx payload" can't be to a filecoin address?
To: embryoAddress,
}
msgFromEmbryo, err = client.GasEstimateMessageGas(ctx, msgFromEmbryo, nil, types.EmptyTSK)
require.NoError(t, err)

txArgs, err := eth.NewEthTxArgsFromMessage(msgFromEmbryo)
require.NoError(t, err)

digest, err := txArgs.OriginalRlpMsg()
require.NoError(t, err)

siggy, err := client.WalletSign(ctx, embryoAddress, digest)
require.NoError(t, err)

smFromEmbryoCid, err := client.MpoolPush(ctx, &types.SignedMessage{Message: *msgFromEmbryo, Signature: *siggy})
require.NoError(t, err)

mLookup, err = client.StateWaitMsg(ctx, smFromEmbryoCid, 3, api.LookbackNoLimit, true)
require.NoError(t, err)
require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode)

// confirm ugly Embryo duckling has turned into a beautiful EthAccount swan

eoaActor, err := client.StateGetActor(ctx, embryoAddress, types.EmptyTSK)
require.NoError(t, err)

require.False(t, builtin.IsEmbryoActor(eoaActor.Code))
require.True(t, builtin.IsEthAccountActor(eoaActor.Code))

// Send another message, it should succeed without any code CID changes

msgFromEmbryo = &types.Message{
From: embryoAddress,
To: embryoAddress,
}

msgFromEmbryo, err = client.GasEstimateMessageGas(ctx, msgFromEmbryo, nil, types.EmptyTSK)
require.NoError(t, err)

txArgs, err = eth.NewEthTxArgsFromMessage(msgFromEmbryo)
require.NoError(t, err)

digest, err = txArgs.OriginalRlpMsg()
require.NoError(t, err)

siggy, err = client.WalletSign(ctx, embryoAddress, digest)
require.NoError(t, err)

smFromEmbryoCid, err = client.MpoolPush(ctx, &types.SignedMessage{Message: *msgFromEmbryo, Signature: *siggy})
require.NoError(t, err)

mLookup, err = client.StateWaitMsg(ctx, smFromEmbryoCid, 3, api.LookbackNoLimit, true)
require.NoError(t, err)
require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode)

// confirm no changes in code CID

eoaActor, err = client.StateGetActor(ctx, embryoAddress, types.EmptyTSK)
require.NoError(t, err)

require.False(t, builtin.IsEmbryoActor(eoaActor.Code))
require.True(t, builtin.IsEthAccountActor(eoaActor.Code))
}