Merge pull request #9831 from filecoin-project/asr/account-abstraction
Account abstraction
This commit is contained in:
commit
503bdb5c34
@ -764,6 +764,11 @@ workflows:
|
|||||||
suite: itest-dup_mpool_messages
|
suite: itest-dup_mpool_messages
|
||||||
target: "./itests/dup_mpool_messages_test.go"
|
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:
|
- test:
|
||||||
name: test-itest-fevm
|
name: test-itest-fevm
|
||||||
suite: itest-fevm
|
suite: itest-fevm
|
||||||
|
@ -274,6 +274,24 @@ func IsPaymentChannelActor(c cid.Cid) bool {
|
|||||||
return false
|
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 {
|
func makeAddress(addr string) address.Address {
|
||||||
ret, err := address.NewFromString(addr)
|
ret, err := address.NewFromString(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -153,6 +153,24 @@ func IsPaymentChannelActor(c cid.Cid) bool {
|
|||||||
return false
|
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 {
|
func makeAddress(addr string) address.Address {
|
||||||
ret, err := address.NewFromString(addr)
|
ret, err := address.NewFromString(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package builtin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ipfs/go-cid"
|
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/chain/actors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func IsEmbryo(c cid.Cid) bool {
|
|
||||||
name, _, ok := actors.GetActorMetaByCode(c)
|
|
||||||
return ok && name == "embryo"
|
|
||||||
}
|
|
@ -436,15 +436,15 @@ func (filec *FilecoinEC) VerifyWinningPoStProof(ctx context.Context, nv network.
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidForSending(act *types.Actor) bool {
|
func IsValidForSending(act *types.Actor) bool {
|
||||||
if builtin.IsAccountActor(act.Code) {
|
if builtin.IsAccountActor(act.Code) || builtin.IsEthAccountActor(act.Code) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: Allow Eth embryos to send messages
|
if !builtin.IsEmbryoActor(act.Code) || act.Address == nil || act.Address.Protocol() != address.Delegated {
|
||||||
if !builtin.IsEmbryo(act.Code) || act.Address == nil || act.Address.Protocol() != address.Delegated {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
id, _, err := varint.FromUvarint(act.Address.Payload())
|
id, _, err := varint.FromUvarint(act.Address.Payload())
|
||||||
return err == nil && id == builtintypes.EthereumAddressManagerActorID
|
return err == nil && id == builtintypes.EthereumAddressManagerActorID
|
||||||
}
|
}
|
||||||
@ -521,7 +521,7 @@ func (filec *FilecoinEC) checkBlockMessages(ctx context.Context, b *types.FullBl
|
|||||||
return xerrors.Errorf("failed to get actor: %w", err)
|
return xerrors.Errorf("failed to get actor: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isValidForSending(act) {
|
if !IsValidForSending(act) {
|
||||||
return xerrors.New("Sender must be an account actor")
|
return xerrors.New("Sender must be an account actor")
|
||||||
}
|
}
|
||||||
nonces[sender] = act.Nonce
|
nonces[sender] = act.Nonce
|
||||||
|
@ -34,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"github.com/filecoin-project/lotus/api"
|
"github.com/filecoin-project/lotus/api"
|
||||||
"github.com/filecoin-project/lotus/build"
|
"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/eth"
|
||||||
"github.com/filecoin-project/lotus/chain/stmgr"
|
"github.com/filecoin-project/lotus/chain/stmgr"
|
||||||
"github.com/filecoin-project/lotus/chain/store"
|
"github.com/filecoin-project/lotus/chain/store"
|
||||||
@ -806,7 +807,7 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error {
|
|||||||
if m.Signature.Type == crypto.SigTypeDelegated {
|
if m.Signature.Type == crypto.SigTypeDelegated {
|
||||||
txArgs, err := eth.NewEthTxArgsFromMessage(&m.Message)
|
txArgs, err := eth.NewEthTxArgsFromMessage(&m.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return xerrors.Errorf("failed to convert to eth tx args: %w", err)
|
||||||
}
|
}
|
||||||
msg, err := txArgs.OriginalRlpMsg()
|
msg, err := txArgs.OriginalRlpMsg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -873,18 +874,28 @@ func (mp *MessagePool) addTs(ctx context.Context, m *types.SignedMessage, curTs
|
|||||||
mp.lk.Lock()
|
mp.lk.Lock()
|
||||||
defer mp.lk.Unlock()
|
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)
|
publish, err := mp.verifyMsgBeforeAdd(ctx, m, curTs, local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, xerrors.Errorf("verify msg failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mp.checkBalance(ctx, m, curTs); err != nil {
|
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)
|
err = mp.addLocked(ctx, m, !local, untrusted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, xerrors.Errorf("failed to add locked: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if local {
|
if local {
|
||||||
|
@ -155,7 +155,7 @@ func (tma *testMpoolAPI) GetActorAfter(addr address.Address, ts *types.TipSet) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &types.Actor{
|
return &types.Actor{
|
||||||
Code: builtin2.StorageMarketActorCodeID,
|
Code: builtin2.AccountActorCodeID,
|
||||||
Nonce: nonce,
|
Nonce: nonce,
|
||||||
Balance: balance,
|
Balance: balance,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -284,10 +284,15 @@ func TryEthAddressFromFilecoinAddress(addr address.Address, allowId bool) (EthAd
|
|||||||
|
|
||||||
func EthAddressFromFilecoinAddress(addr address.Address) (EthAddress, error) {
|
func EthAddressFromFilecoinAddress(addr address.Address) (EthAddress, error) {
|
||||||
ethAddr, ok, err := TryEthAddressFromFilecoinAddress(addr, true)
|
ethAddr, ok, err := TryEthAddressFromFilecoinAddress(addr, true)
|
||||||
if !ok && err == nil {
|
if err != nil {
|
||||||
err = xerrors.Errorf("failed to convert filecoin address %s to an equivalent eth address", addr)
|
return EthAddress{}, xerrors.Errorf("failed to try converting filecoin to eth addr: %w", err)
|
||||||
}
|
}
|
||||||
return ethAddr, err
|
|
||||||
|
if !ok {
|
||||||
|
return EthAddress{}, xerrors.Errorf("failed to convert filecoin address %s to an equivalent eth address", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ethAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EthAddressFromHex(s string) (EthAddress, error) {
|
func EthAddressFromHex(s string) (EthAddress, error) {
|
||||||
|
@ -285,7 +285,7 @@ func DecodeParams(b []byte, out interface{}) error {
|
|||||||
|
|
||||||
func DumpActorState(i *ActorRegistry, act *types.Actor, b []byte) (interface{}, error) {
|
func DumpActorState(i *ActorRegistry, act *types.Actor, b []byte) (interface{}, error) {
|
||||||
// Account & Embryo code special case
|
// 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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
127
itests/eth_account_abstraction_test.go
Normal file
127
itests/eth_account_abstraction_test.go
Normal file
@ -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))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user