From 50ba403d62823589c5baa4307463a1c036e2204b Mon Sep 17 00:00:00 2001 From: simlecode <69969590+simlecode@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:50:31 +0800 Subject: [PATCH] feat: f3 activation contract --- app/submodule/f3/f3_submodule.go | 2 +- go.mod | 2 +- go.sum | 4 +- pkg/vf3/config.go | 16 ++ pkg/vf3/manifest.go | 269 +++++++++++++++++- pkg/vf3/manifest_test.go | 44 +++ .../testdata/contract_manifest_golden.json | 58 ++++ pkg/vf3/testdata/contract_return.hex | 1 + venus-devtool/go.mod | 2 +- venus-devtool/go.sum | 4 +- venus-shared/actors/types/eth.go | 59 ++++ 11 files changed, 443 insertions(+), 18 deletions(-) create mode 100644 pkg/vf3/manifest_test.go create mode 100644 pkg/vf3/testdata/contract_manifest_golden.json create mode 100644 pkg/vf3/testdata/contract_return.hex diff --git a/app/submodule/f3/f3_submodule.go b/app/submodule/f3/f3_submodule.go index 7ccabd6c3d..e99844d97a 100644 --- a/app/submodule/f3/f3_submodule.go +++ b/app/submodule/f3/f3_submodule.go @@ -37,7 +37,7 @@ func NewF3Submodule(ctx context.Context, return nil, err } - provider, err := vf3.NewManifestProvider(ctx, network.F3Cfg, chain.ChainReader, network.Pubsub, repo.MetaDatastore()) + provider, err := vf3.NewManifestProvider(ctx, network.F3Cfg, chain.ChainReader, network.Pubsub, repo.MetaDatastore(), chain.API()) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 92bac823e0..561a0e5edc 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/filecoin-project/go-commp-utils v0.1.3 github.com/filecoin-project/go-crypto v0.1.0 github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc6 - github.com/filecoin-project/go-f3 v0.8.0 + github.com/filecoin-project/go-f3 v0.8.1 github.com/filecoin-project/go-fil-commcid v0.2.0 github.com/filecoin-project/go-fil-markets v1.28.2 github.com/filecoin-project/go-jsonrpc v0.1.5 diff --git a/go.sum b/go.sum index e1560fdf44..5f4223a18f 100644 --- a/go.sum +++ b/go.sum @@ -1702,8 +1702,8 @@ github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc6 h1:EsbXTWsBKT764qtX4M github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc6/go.mod h1:cX1acvFVWC5EXnnmFPWEFXbO7nLUdSZa+nqgi1QpTpw= github.com/filecoin-project/go-ds-versioning v0.1.2 h1:to4pTadv3IeV1wvgbCbN6Vqd+fu+7tveXgv/rCEZy6w= github.com/filecoin-project/go-ds-versioning v0.1.2/go.mod h1:C9/l9PnB1+mwPa26BBVpCjG/XQCB0yj/q5CK2J8X1I4= -github.com/filecoin-project/go-f3 v0.8.0 h1:Zm2NIhryHFudh3QZ5X0qXAqGm/rMc9zjndGLd4vL07A= -github.com/filecoin-project/go-f3 v0.8.0/go.mod h1:zNFGuBM+fYuGXk2fpzl6wW4g2Gyrxgg6z2IVSoGt+60= +github.com/filecoin-project/go-f3 v0.8.1 h1:stlc3ZW5rHXVdvTbCreKh1475GB7noTtvfPGraOkdRA= +github.com/filecoin-project/go-f3 v0.8.1/go.mod h1:dAqNQ59L/zxjt32KI5kM8gzPtN8odwYHTjNcEq5wp1s= github.com/filecoin-project/go-fil-commcid v0.0.0-20201016201715-d41df56b4f6a/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= github.com/filecoin-project/go-fil-commcid v0.2.0 h1:B+5UX8XGgdg/XsdUpST4pEBviKkFOw+Fvl2bLhSKGpI= diff --git a/pkg/vf3/config.go b/pkg/vf3/config.go index 2c68f69b85..4c2bb2d608 100644 --- a/pkg/vf3/config.go +++ b/pkg/vf3/config.go @@ -1,6 +1,7 @@ package vf3 import ( + "os" "time" "github.com/ipfs/go-cid" @@ -32,6 +33,10 @@ type Config struct { // TESTINGAllowDynamicFinalize allow dynamic manifests to finalize tipsets. DO NOT ENABLE // THIS IN PRODUCTION! AllowDynamicFinalize bool + + // ContractAddress specifies the address of the contract carring F3 parameters + ContractAddress string + ContractPollInterval time.Duration } // NewManifest constructs a sane F3 manifest based on the passed parameters. This function does not @@ -81,11 +86,22 @@ func NewConfig(nn string, netCfg *config.NetworkParamsConfig) (*Config, error) { if err != nil { return nil, err } + pollInterval := 15 * time.Minute + if envVar := os.Getenv("VENUS_F3_POLL_INTERVAL"); len(envVar) != 0 { + d, err := time.ParseDuration(envVar) + if err != nil { + log.Errorf("invalid duration in VENUS_F3_POLL_INTERVAL, defaulting to %v", pollInterval) + } else { + pollInterval = d + } + + } c := &Config{ BaseNetworkName: gpbft.NetworkName(nn), PrioritizeStaticManifest: true, DynamicManifestProvider: manifestServerID, AllowDynamicFinalize: false, + ContractPollInterval: pollInterval, } if netCfg.F3BootstrapEpoch >= 0 { // todo: diff --git a/pkg/vf3/manifest.go b/pkg/vf3/manifest.go index 5be1f28efa..0c118bc175 100644 --- a/pkg/vf3/manifest.go +++ b/pkg/vf3/manifest.go @@ -1,17 +1,29 @@ package vf3 import ( + "bytes" + "compress/flate" "context" + "encoding/binary" + "encoding/json" "fmt" + "io" + "math" "strings" + "time" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" pubsub "github.com/libp2p/go-libp2p-pubsub" + "golang.org/x/sync/errgroup" "github.com/filecoin-project/go-f3/ec" + "github.com/filecoin-project/go-f3/gpbft" "github.com/filecoin-project/go-f3/manifest" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/venus/pkg/chain" + "github.com/filecoin-project/venus/venus-shared/types" + must "github.com/filecoin-project/venus/venus-shared/utils" ) type headGetter struct { @@ -32,12 +44,30 @@ func (hg *headGetter) GetHead(_ context.Context) (ec.TipSet, error) { // message topic will be filtered var MaxDynamicManifestChangesAllowed = 1000 -func NewManifestProvider(ctx context.Context, config *Config, cs *chain.Store, ps *pubsub.PubSub, mds datastore.Datastore) (prov manifest.ManifestProvider, err error) { +func NewManifestProvider(ctx context.Context, + config *Config, + cs *chain.Store, + ps *pubsub.PubSub, + mds datastore.Datastore, + stateCaller StateCaller, +) (prov manifest.ManifestProvider, err error) { + var primaryManifest manifest.ManifestProvider + if config.StaticManifest != nil { + log.Infof("using static maniest as primary") + primaryManifest, err = manifest.NewStaticManifestProvider(config.StaticManifest) + } else if config.ContractAddress != "" { + log.Infow("using contract maniest as primary", "address", config.ContractAddress) + primaryManifest, err = NewContractManifestProvider(ctx, config, stateCaller) + } + if err != nil { + return nil, fmt.Errorf("creating primary manifest: %w", err) + } + if config.DynamicManifestProvider == "" { - if config.StaticManifest == nil { + if config.StaticManifest == nil && config.ContractAddress == "" { return manifest.NoopManifestProvider{}, nil } - return manifest.NewStaticManifestProvider(config.StaticManifest) + return primaryManifest, nil } opts := []manifest.DynamicManifestProviderOption{ @@ -46,12 +76,6 @@ func NewManifestProvider(ctx context.Context, config *Config, cs *chain.Store, p ), } - if config.StaticManifest != nil { - opts = append(opts, - manifest.DynamicManifestProviderWithInitialManifest(config.StaticManifest), - ) - } - if config.AllowDynamicFinalize { log.Error("dynamic F3 manifests are allowed to finalize tipsets, do not enable this in production!") } @@ -81,10 +105,233 @@ func NewManifestProvider(ctx context.Context, config *Config, cs *chain.Store, p if err != nil { return nil, err } - if config.PrioritizeStaticManifest && config.StaticManifest != nil { + if config.PrioritizeStaticManifest && primaryManifest != nil { prov, err = manifest.NewFusingManifestProvider(ctx, - &headGetter{cs}, prov, config.StaticManifest) + &headGetter{cs}, prov, primaryManifest) } return prov, err } + +type StateCaller interface { + StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *types.InvocResult, err error) +} + +type ContractManifestProvider struct { + address string + networkName gpbft.NetworkName + stateCaller StateCaller + pollInterval time.Duration + + manifestChanges chan *manifest.Manifest + + errgrp *errgroup.Group + runningCtx context.Context + cancel context.CancelFunc +} + +func NewContractManifestProvider(ctx context.Context, config *Config, stateCaller StateCaller) (*ContractManifestProvider, error) { + ctx, cancel := context.WithCancel(context.WithoutCancel(ctx)) + errgrp, ctx := errgroup.WithContext(ctx) + return &ContractManifestProvider{ + stateCaller: stateCaller, + address: config.ContractAddress, + networkName: config.BaseNetworkName, + pollInterval: config.ContractPollInterval, + + manifestChanges: make(chan *manifest.Manifest, 1), + + errgrp: errgrp, + runningCtx: ctx, + cancel: cancel, + }, nil +} + +func (cmp *ContractManifestProvider) Start(context.Context) error { + // no address, nothing to do + if len(cmp.address) == 0 { + // send nil so fusing knows we have nothing + log.Infof("contract manifest provider, address unknown, exiting") + cmp.manifestChanges <- nil + return nil + } + + var knownManifest *manifest.Manifest + knownManifest, err := cmp.fetchManifest(cmp.runningCtx) + if err != nil { + log.Warnw("got error while fetching manifest from contract", "error", err) + } + cmp.manifestChanges <- knownManifest + + cmp.errgrp.Go(func() error { + t := time.NewTicker(cmp.pollInterval) + defer t.Stop() + + loop: + for cmp.runningCtx.Err() == nil { + select { + case <-t.C: + m, err := cmp.fetchManifest(cmp.runningCtx) + if err != nil { + log.Warnw("got error while fetching manifest from contract", "error", err) + continue loop + } + + if knownManifest.Equal(m) { + continue loop + } + + c, err := m.Cid() + if err != nil { + log.Errorf("got error while computing manifest CID") + } + + if m != nil { + log.Infow("new manifest from contract", "enabled", true, + "bootstrapEpoch", m.BootstrapEpoch, + "manifestCID", c) + } else { + log.Info("new manifest from contract", "enabled", false) + } + cmp.manifestChanges <- m + knownManifest = m + case <-cmp.runningCtx.Done(): + } + } + + return nil + }) + return nil +} + +func decompressManifest(compressedManifest []byte) (*manifest.Manifest, error) { + reader := io.LimitReader(flate.NewReader(bytes.NewReader(compressedManifest)), 1<<20) + var m manifest.Manifest + err := json.NewDecoder(reader).Decode(&m) + if err != nil { + return nil, err + } + return &m, nil +} + +func (cmp *ContractManifestProvider) fetchManifest(ctx context.Context) (*manifest.Manifest, error) { + ethReturn, err := cmp.callContract(ctx) + if err != nil { + return nil, fmt.Errorf("calling contract at %s: %w", cmp.address, err) + } + if len(ethReturn) == 0 { + return nil, nil + } + + activationEpoch, compressedManifest, err := parseContractReturn(ethReturn) + if err != nil { + return nil, fmt.Errorf("parsing contract information: %w", err) + } + + if activationEpoch == math.MaxUint64 || len(compressedManifest) == 0 { + return nil, nil + } + + m, err := decompressManifest(compressedManifest) + if err != nil { + return nil, fmt.Errorf("got error while decoding manifest: %w", err) + } + + if m.BootstrapEpoch < 0 || uint64(m.BootstrapEpoch) != activationEpoch { + return nil, fmt.Errorf("bootstrap epoch does not match: %d != %d", m.BootstrapEpoch, activationEpoch) + } + + if err := m.Validate(); err != nil { + return nil, fmt.Errorf("manifest does not validate: %w", err) + } + + if m.NetworkName != cmp.networkName { + return nil, fmt.Errorf("network name does not match, expected: %s, got: %s", + cmp.networkName, m.NetworkName) + } + + return m, nil +} + +func parseContractReturn(retBytes []byte) (uint64, []byte, error) { + // 3*32 because there should be 3 slots minimum + if len(retBytes) < 3*32 { + return 0, nil, fmt.Errorf("no activation information") + } + + var slot []byte + // split off first slot + slot, retBytes = retBytes[:32], retBytes[32:] + // it is uint64 so we want the last 8 bytes + slot = slot[24:32] + activationEpoch := binary.BigEndian.Uint64(slot) + + // next slot is the offest to variable length bytes + // it is always the same 0x00000...0040 + slot, retBytes = retBytes[:32], retBytes[32:] + for i := 0; i < 31; i++ { + if slot[i] != 0 { + return 0, nil, fmt.Errorf("wrong value for offest (padding): slot[%d] = 0x%x != 0x00", i, slot[i]) + } + } + if slot[31] != 0x40 { + return 0, nil, fmt.Errorf("wrong value for offest : slot[31] = 0x%x != 0x40", slot[31]) + } + + // finally after that there are manifest bytes + // starts with length in a full slot, slot no 3 + slot, retBytes = retBytes[:32], retBytes[32:] + slot = slot[24:32] + pLen := binary.BigEndian.Uint64(slot) + if pLen > 4<<10 { + return 0, nil, fmt.Errorf("too long declared payload: %d > %d", pLen, 4<<10) + } + payloadLength := int(pLen) + + if payloadLength > len(retBytes) { + return 0, nil, fmt.Errorf("not enough remaining bytes: %d > %d", payloadLength, retBytes) + } + + return activationEpoch, retBytes[:payloadLength], nil +} + +func (cmp *ContractManifestProvider) callContract(ctx context.Context) ([]byte, error) { + address, err := types.ParseEthAddress(cmp.address) + if err != nil { + return nil, fmt.Errorf("trying to parse contract address: %s: %w", cmp.address, err) + } + + ethCall := types.EthCall{ + To: &address, + Data: must.One(types.DecodeHexString("0x2587660d")), // method ID of activationInformation() + } + + fMessage, err := ethCall.ToFilecoinMessage() + if err != nil { + return nil, fmt.Errorf("converting to filecoin message: %w", err) + } + + msgRes, err := cmp.stateCaller.StateCall(ctx, fMessage, types.EmptyTSK) + if err != nil { + return nil, fmt.Errorf("state call error: %w", err) + } + if msgRes.MsgRct.ExitCode != 0 { + return nil, fmt.Errorf("message returned exit code %v: %v", msgRes.MsgRct.ExitCode, msgRes.Error) + } + + var ethReturn abi.CborBytes + err = ethReturn.UnmarshalCBOR(bytes.NewReader(msgRes.MsgRct.Return)) + if err != nil { + return nil, fmt.Errorf("could not decode return value: %w", err) + } + return []byte(ethReturn), nil +} + +func (cmp *ContractManifestProvider) Stop(context.Context) error { + cmp.cancel() + return cmp.errgrp.Wait() +} + +func (cmp *ContractManifestProvider) ManifestUpdates() <-chan *manifest.Manifest { + return cmp.manifestChanges +} diff --git a/pkg/vf3/manifest_test.go b/pkg/vf3/manifest_test.go new file mode 100644 index 0000000000..0c505228b3 --- /dev/null +++ b/pkg/vf3/manifest_test.go @@ -0,0 +1,44 @@ +package vf3 + +import ( + _ "embed" + "encoding/hex" + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-f3/manifest" +) + +//go:embed testdata/contract_manifest_golden.json +var manifestJSONBytes []byte + +//go:embed testdata/contract_return.hex +var hexReturn string + +func TestContractManifest_ParseEthData(t *testing.T) { + var manifestGolden manifest.Manifest + err := json.Unmarshal(manifestJSONBytes, &manifestGolden) + require.NoError(t, manifestGolden.Validate(), "golden manifest is not valid") + + require.NoErrorf(t, err, "did manifest format change?") + decodedHex, err := hex.DecodeString(strings.Trim(hexReturn, "\n")) + require.NoError(t, err, "failed to decode hex string") + + activationEpoch, compressedManifest, err := parseContractReturn(decodedHex) + require.NoError(t, err, "parseContractReturn failed") + + require.Equal(t, uint64(5000000), activationEpoch, "activationEpoch mismatch") + + manifest, err := decompressManifest(compressedManifest) + require.NoError(t, err, "decompressManifest failed") + + require.NoError(t, manifest.Validate(), "manifest is not valid") + + manifestJSON, err := json.MarshalIndent(manifest, "", " ") + require.NoError(t, err, "failed to marshal manifest") + + require.JSONEq(t, string(manifestJSONBytes), string(manifestJSON), "manifest JSON mismatch") +} diff --git a/pkg/vf3/testdata/contract_manifest_golden.json b/pkg/vf3/testdata/contract_manifest_golden.json new file mode 100644 index 0000000000..f7aad5434c --- /dev/null +++ b/pkg/vf3/testdata/contract_manifest_golden.json @@ -0,0 +1,58 @@ +{ + "Pause": false, + "ProtocolVersion": 5, + "InitialInstance": 0, + "BootstrapEpoch": 5000000, + "NetworkName": "filecoin", + "ExplicitPower": null, + "IgnoreECPower": false, + "InitialPowerTable": null, + "CommitteeLookback": 10, + "CatchUpAlignment": 15000000000, + "Gpbft": { + "Delta": 6000000000, + "DeltaBackOffExponent": 2, + "QualityDeltaMultiplier": 1, + "MaxLookaheadRounds": 5, + "ChainProposedLength": 100, + "RebroadcastBackoffBase": 6000000000, + "RebroadcastBackoffExponent": 1.3, + "RebroadcastBackoffSpread": 0.1, + "RebroadcastBackoffMax": 60000000000 + }, + "EC": { + "Period": 30000000000, + "Finality": 900, + "DelayMultiplier": 2, + "BaseDecisionBackoffTable": [ + 1.3, + 1.69, + 2.2, + 2.86, + 3.71, + 4.83, + 6.27, + 7.5 + ], + "HeadLookback": 0, + "Finalize": true + }, + "CertificateExchange": { + "ClientRequestTimeout": 10000000000, + "ServerRequestTimeout": 60000000000, + "MinimumPollInterval": 30000000000, + "MaximumPollInterval": 120000000000 + }, + "PubSub": { + "CompressionEnabled": false + }, + "ChainExchange": { + "SubscriptionBufferSize": 32, + "MaxChainLength": 100, + "MaxInstanceLookahead": 10, + "MaxDiscoveredChainsPerInstance": 1000, + "MaxWantedChainsPerInstance": 1000, + "RebroadcastInterval": 2000000000, + "MaxTimestampAge": 8000000000 + } +} diff --git a/pkg/vf3/testdata/contract_return.hex b/pkg/vf3/testdata/contract_return.hex new file mode 100644 index 0000000000..462337c723 --- /dev/null +++ b/pkg/vf3/testdata/contract_return.hex @@ -0,0 +1 @@ +00000000000000000000000000000000000000000000000000000000004C4B400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000023D8554C172DA3014BCF3150CE78E079B06486FC1D0363321A590B6874E0FB2FC8C35C8922BC90D6926FFDE27D9C27620539F9EB52B7977DF939F07C3E168432A0DA30FC38C700DEFDC8A924652C9BF83D24C0AC4AEDCFAAD6086117E2BB42182DA3D63B7BE90D268A348B92A25CD2D7DEC1E07DE837994EA704F0ABB6194310E54323172E0EA58724699D9C84750088B8AF3FA537B2115AC620FB4E21A110E78200987EEB65816053306E04ECA4342E801C1B0D6111343F36FE50D677B5180301669747AA99FCA24B3EBCFF882AF4BE086E0EBB4C7F2C0024FFF9265E8408AFAB8A881BF568433F3E458EB8A1B861E9D89B021ACC9D1EA233990742B2B916A1FB1D5991326B003A5D490DE81D89BDC99F01FDF42A2244929D1C64A9059B620AE7F6732CF991DB161307993B62B152AB3ED0DC2374968A2F7D131125FEAA6C66D861B504CDAA326E3D7F23E32E17242F0BA1B2D79EA85E653B52E9740999DC846826FFF4FC718B69E6C39BDF67514446D399FFA7A12CC425FBF0FE6A7ADD3209AF97A165CB9EA5723E233E6D219ADBE95BF568B51159C9288411996314A0CAC8E3427620F6D34311A14660BBF2BD0E68115202B5377FA55503B507F409D11A767C43513ACA88A8DE478470DEE22FC62F2D8BA0BBC30BAD0CB4D95ECAAA4235A16381CDA3661256CFCA9BF9CAD693BC0E776F1184D152B8DED5F9565A076756293A895E5F65E187A84FC5FE774734E57BBC6974C53893941EA0ED138799D1F55D873FF83A0EDFFF13A23DF4929BA10A6ED099E509437CEF0BC17E3E065F00F000000 diff --git a/venus-devtool/go.mod b/venus-devtool/go.mod index e0e0f610a4..9f269b0b03 100644 --- a/venus-devtool/go.mod +++ b/venus-devtool/go.mod @@ -6,7 +6,7 @@ require ( github.com/filecoin-project/go-address v1.2.0 github.com/filecoin-project/go-bitfield v0.2.4 github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc7 - github.com/filecoin-project/go-f3 v0.8.0 + github.com/filecoin-project/go-f3 v0.8.1 github.com/filecoin-project/go-fil-markets v1.28.3 github.com/filecoin-project/go-jsonrpc v0.7.0 github.com/filecoin-project/go-state-types v0.16.0-rc1 diff --git a/venus-devtool/go.sum b/venus-devtool/go.sum index d795b10740..d1199dc936 100644 --- a/venus-devtool/go.sum +++ b/venus-devtool/go.sum @@ -216,8 +216,8 @@ github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc7 h1:v+zJS5B6pA3ptWZS4t github.com/filecoin-project/go-data-transfer/v2 v2.0.0-rc7/go.mod h1:V3Y4KbttaCwyg1gwkP7iai8CbQx4mZUGjd3h9GZWLKE= github.com/filecoin-project/go-ds-versioning v0.1.2 h1:to4pTadv3IeV1wvgbCbN6Vqd+fu+7tveXgv/rCEZy6w= github.com/filecoin-project/go-ds-versioning v0.1.2/go.mod h1:C9/l9PnB1+mwPa26BBVpCjG/XQCB0yj/q5CK2J8X1I4= -github.com/filecoin-project/go-f3 v0.8.0 h1:Zm2NIhryHFudh3QZ5X0qXAqGm/rMc9zjndGLd4vL07A= -github.com/filecoin-project/go-f3 v0.8.0/go.mod h1:zNFGuBM+fYuGXk2fpzl6wW4g2Gyrxgg6z2IVSoGt+60= +github.com/filecoin-project/go-f3 v0.8.1 h1:stlc3ZW5rHXVdvTbCreKh1475GB7noTtvfPGraOkdRA= +github.com/filecoin-project/go-f3 v0.8.1/go.mod h1:dAqNQ59L/zxjt32KI5kM8gzPtN8odwYHTjNcEq5wp1s= github.com/filecoin-project/go-fil-commcid v0.2.0 h1:B+5UX8XGgdg/XsdUpST4pEBviKkFOw+Fvl2bLhSKGpI= github.com/filecoin-project/go-fil-commcid v0.2.0/go.mod h1:8yigf3JDIil+/WpqR5zoKyP0jBPCOGtEqq/K1CcMy9Q= github.com/filecoin-project/go-fil-commp-hashhash v0.2.0 h1:HYIUugzjq78YvV3vC6rL95+SfC/aSTVSnZSZiDV5pCk= diff --git a/venus-shared/actors/types/eth.go b/venus-shared/actors/types/eth.go index 9deca4ef08..0db8685033 100644 --- a/venus-shared/actors/types/eth.go +++ b/venus-shared/actors/types/eth.go @@ -21,6 +21,7 @@ import ( "github.com/filecoin-project/go-state-types/big" builtintypes "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/venus/pkg/constants" + "github.com/filecoin-project/venus/venus-shared/actors" ) var expectedHashPrefix = cid.Prefix{ @@ -248,6 +249,64 @@ type EthCall struct { Data EthBytes `json:"data"` } +func (c *EthCall) ToFilecoinMessage() (*Message, error) { + var from address.Address + if c.From == nil || *c.From == (EthAddress{}) { + // Send from the filecoin "system" address. + var err error + from, err = (EthAddress{}).ToFilecoinAddress() + if err != nil { + return nil, fmt.Errorf("failed to construct the ethereum system address: %w", err) + } + } else { + // The from address must be translatable to an f4 address. + var err error + from, err = c.From.ToFilecoinAddress() + if err != nil { + return nil, fmt.Errorf("failed to translate sender address (%s): %w", c.From.String(), err) + } + if p := from.Protocol(); p != address.Delegated { + return nil, fmt.Errorf("expected a class 4 address, got: %d: %w", p, err) + } + } + + var params []byte + if len(c.Data) > 0 { + initcode := abi.CborBytes(c.Data) + params2, err := actors.SerializeParams(&initcode) + if err != nil { + return nil, fmt.Errorf("failed to serialize params: %w", err) + } + params = params2 + } + + var to address.Address + var method abi.MethodNum + if c.To == nil { + // this is a contract creation + to = builtintypes.EthereumAddressManagerActorAddr + method = builtintypes.MethodsEAM.CreateExternal + } else { + addr, err := c.To.ToFilecoinAddress() + if err != nil { + return nil, fmt.Errorf("cannot get Filecoin address: %w", err) + } + to = addr + method = builtintypes.MethodsEVM.InvokeContract + } + + return &Message{ + From: from, + To: to, + Value: big.Int(c.Value), + Method: method, + Params: params, + GasLimit: constants.BlockGasLimit, + GasFeeCap: big.Zero(), + GasPremium: big.Zero(), + }, nil +} + func (c *EthCall) UnmarshalJSON(b []byte) error { type TempEthCall EthCall var params TempEthCall