diff --git a/.circleci/config.yml b/.circleci/config.yml index b9aa6153f..aa6e19a44 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -355,56 +355,6 @@ jobs: - run: ./scripts/generate-checksums.sh - run: ./scripts/publish-checksums.sh - build-appimage: - machine: - image: ubuntu-2004:202111-02 - steps: - - checkout - - attach_workspace: - at: /tmp/workspace - - run: - name: Update Go - command: | - sudo rm -rf /usr/local/go && \ - curl -L https://golang.org/dl/go`cat GO_VERSION_MIN`.linux-amd64.tar.gz -o /tmp/go.tar.gz && \ - sudo tar -C /usr/local -xvf /tmp/go.tar.gz - - run: go version - - run: - name: install appimage-builder - command: | - # appimage-builder requires /dev/snd to exist. It creates containers during the testing phase - # that pass sound devices from the host to the testing container. (hard coded!) - # https://github.com/AppImageCrafters/appimage-builder/blob/master/appimagebuilder/modules/test/execution_test.py#L54 - # Circleci doesn't provide a working sound device; this is enough to fake it. - if [ ! -e /dev/snd ] - then - sudo mkdir /dev/snd - sudo mknod /dev/snd/ControlC0 c 1 2 - fi - - # docs: https://appimage-builder.readthedocs.io/en/latest/intro/install.html - sudo apt update - sudo apt install -y python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace - sudo curl -Lo /usr/local/bin/appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage - sudo chmod +x /usr/local/bin/appimagetool - sudo pip3 install appimage-builder - - run: - name: install lotus dependencies - command: sudo apt install ocl-icd-opencl-dev libhwloc-dev - - run: - name: build appimage - command: | - sed -i "s/version: latest/version: ${CIRCLE_TAG:-latest}/" AppImageBuilder.yml - make appimage - - run: | - mkdir -p /tmp/workspace/appimage && \ - mv Lotus-*.AppImage /tmp/workspace/appimage/ - - persist_to_workspace: - root: /tmp/workspace - paths: - - appimage - - gofmt: executor: golang working_directory: ~/lotus @@ -572,7 +522,7 @@ jobs: equal: [ mainnet, <> ] steps: - when: - condition: > + condition: <> steps: - docker/build: image: filecoin/<> @@ -583,7 +533,7 @@ jobs: command: | docker push filecoin/<>:<> if [[ ! -z $CIRCLE_SHA ]]; then - docker image tag filecoin/<>:<>> filecoin/<>:"${CIRCLE_SHA:0:7}" + docker image tag filecoin/<>:<> filecoin/<>:"${CIRCLE_SHA:0:7}" docker push filecoin/<>:"${CIRCLE_SHA:0:7}" fi if [[ ! -z $CIRCLE_TAG ]]; then @@ -611,7 +561,7 @@ jobs: - run: name: Docker push command: | - docker push filecoin/<>:<>-<> + docker push filecoin/<>:<> if [[ ! -z $CIRCLE_SHA ]]; then docker image tag filecoin/<>:<>-<> filecoin/<>:"${CIRCLE_SHA:0:7}"-<> docker push filecoin/<>:"${CIRCLE_SHA:0:7}"-<> @@ -785,6 +735,11 @@ workflows: - build suite: itest-eth_balance target: "./itests/eth_balance_test.go" + - test: + name: test-itest-eth_block_hash + suite: itest-eth_block_hash + target: "./itests/eth_block_hash_test.go" + - test: name: test-itest-eth_deploy requires: @@ -797,6 +752,11 @@ workflows: - build suite: itest-eth_filter target: "./itests/eth_filter_test.go" + - test: + name: test-itest-eth_hash_lookup + suite: itest-eth_hash_lookup + target: "./itests/eth_hash_lookup_test.go" + - test: name: test-itest-eth_transactions requires: @@ -1182,71 +1142,6 @@ workflows: branches: only: - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-appimage: - name: "Build AppImage" - filters: - branches: - only: - - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - publish: - name: "Publish AppImage" - appimage: true - requires: - - "Build AppImage" - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - publish-snapcraft: - name: "Publish Snapcraft (lotus / stable)" - channel: stable - snap-name: lotus - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+$/ - - publish-snapcraft: - name: "Publish Snapcraft (lotus / candidate)" - channel: candidate - snap-name: lotus - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+-rc\d+$/ - - publish-snapcraft: - name: "Publish Snapcraft (lotus-filecoin / stable)" - channel: stable - snap-name: lotus-filecoin - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+$/ - - publish-snapcraft: - name: "Publish Snapcraft (lotus-filecoin / candidate)" - channel: candidate - snap-name: lotus-filecoin - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+-rc\d+$/ - build-docker: name: "Docker push (lotus-all-in-one / stable / mainnet)" image: lotus-all-in-one @@ -1482,14 +1377,6 @@ workflows: only: - master jobs: - - publish-snapcraft: - name: "Publish Snapcraft (lotus / edge)" - channel: edge - snap-name: lotus - - publish-snapcraft: - name: "Publish Snapcraft (lotus-filecoin / edge)" - channel: edge - snap-name: lotus-filecoin - build-docker: name: "Docker (lotus-all-in-one / nightly / mainnet)" image: lotus-all-in-one diff --git a/.circleci/gen.go b/.circleci/gen.go index 6cc9cedb1..5d951027a 100644 --- a/.circleci/gen.go +++ b/.circleci/gen.go @@ -107,13 +107,11 @@ func main() { // form the input data. type data struct { Networks []string - SnapNames []string ItestFiles []string UnitSuites map[string]string } in := data{ Networks: []string{"mainnet", "butterflynet", "calibnet", "debug"}, - SnapNames: []string{"lotus", "lotus-filecoin"}, ItestFiles: itests, UnitSuites: func() map[string]string { ret := make(map[string]string) diff --git a/.circleci/template.yml b/.circleci/template.yml index 1b79e595c..90a204488 100644 --- a/.circleci/template.yml +++ b/.circleci/template.yml @@ -355,56 +355,6 @@ jobs: - run: ./scripts/generate-checksums.sh - run: ./scripts/publish-checksums.sh - build-appimage: - machine: - image: ubuntu-2004:202111-02 - steps: - - checkout - - attach_workspace: - at: /tmp/workspace - - run: - name: Update Go - command: | - sudo rm -rf /usr/local/go && \ - curl -L https://golang.org/dl/go`cat GO_VERSION_MIN`.linux-amd64.tar.gz -o /tmp/go.tar.gz && \ - sudo tar -C /usr/local -xvf /tmp/go.tar.gz - - run: go version - - run: - name: install appimage-builder - command: | - # appimage-builder requires /dev/snd to exist. It creates containers during the testing phase - # that pass sound devices from the host to the testing container. (hard coded!) - # https://github.com/AppImageCrafters/appimage-builder/blob/master/appimagebuilder/modules/test/execution_test.py#L54 - # Circleci doesn't provide a working sound device; this is enough to fake it. - if [ ! -e /dev/snd ] - then - sudo mkdir /dev/snd - sudo mknod /dev/snd/ControlC0 c 1 2 - fi - - # docs: https://appimage-builder.readthedocs.io/en/latest/intro/install.html - sudo apt update - sudo apt install -y python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace - sudo curl -Lo /usr/local/bin/appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage - sudo chmod +x /usr/local/bin/appimagetool - sudo pip3 install appimage-builder - - run: - name: install lotus dependencies - command: sudo apt install ocl-icd-opencl-dev libhwloc-dev - - run: - name: build appimage - command: | - sed -i "s/version: latest/version: ${CIRCLE_TAG:-latest}/" AppImageBuilder.yml - make appimage - - run: | - mkdir -p /tmp/workspace/appimage && \ - mv Lotus-*.AppImage /tmp/workspace/appimage/ - - persist_to_workspace: - root: /tmp/workspace - paths: - - appimage - - gofmt: executor: golang working_directory: ~/lotus @@ -572,7 +522,7 @@ jobs: equal: [ mainnet, <> ] steps: - when: - condition: > + condition: <> steps: - docker/build: image: filecoin/<> @@ -583,7 +533,7 @@ jobs: command: | docker push filecoin/<>:<> if [["[[ ! -z $CIRCLE_SHA ]]"]]; then - docker image tag filecoin/<>:<>> filecoin/<>:"${CIRCLE_SHA:0:7}" + docker image tag filecoin/<>:<> filecoin/<>:"${CIRCLE_SHA:0:7}" docker push filecoin/<>:"${CIRCLE_SHA:0:7}" fi if [["[[ ! -z $CIRCLE_TAG ]]"]]; then @@ -611,7 +561,7 @@ jobs: - run: name: Docker push command: | - docker push filecoin/<>:<>-<> + docker push filecoin/<>:<> if [["[[ ! -z $CIRCLE_SHA ]]"]]; then docker image tag filecoin/<>:<>-<> filecoin/<>:"${CIRCLE_SHA:0:7}"-<> docker push filecoin/<>:"${CIRCLE_SHA:0:7}"-<> @@ -743,51 +693,6 @@ workflows: branches: only: - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ - - build-appimage: - name: "Build AppImage" - filters: - branches: - only: - - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - - publish: - name: "Publish AppImage" - appimage: true - requires: - - "Build AppImage" - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+(-rc\d+)?$/ - [[- range .SnapNames]] - - publish-snapcraft: - name: "Publish Snapcraft ([[.]] / stable)" - channel: stable - snap-name: [[.]] - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+$/ - - publish-snapcraft: - name: "Publish Snapcraft ([[.]] / candidate)" - channel: candidate - snap-name: [[.]] - filters: - branches: - ignore: - - /.*/ - tags: - only: - - /^v\d+\.\d+\.\d+-rc\d+$/ - [[- end]] [[- range .Networks]] - build-docker: name: "Docker push (lotus-all-in-one / stable / [[.]])" @@ -890,12 +795,6 @@ workflows: only: - master jobs: - [[- range .SnapNames]] - - publish-snapcraft: - name: "Publish Snapcraft ([[.]] / edge)" - channel: edge - snap-name: [[.]] - [[- end]] [[- range .Networks]] - build-docker: name: "Docker (lotus-all-in-one / nightly / [[.]])" diff --git a/Makefile b/Makefile index c085faafb..43c362b86 100644 --- a/Makefile +++ b/Makefile @@ -84,12 +84,6 @@ butterflynet: build-devnets interopnet: GOFLAGS+=-tags=interopnet interopnet: build-devnets -wallabynet: GOFLAGS+=-tags=wallabynet -wallabynet: build-devnets - -hyperspacenet: GOFLAGS+=-tags=hyperspacenet -hyperspacenet: build-devnets - lotus: $(BUILD_DEPS) rm -f lotus $(GOCC) build $(GOFLAGS) -o lotus ./cmd/lotus diff --git a/api/api_full.go b/api/api_full.go index 41fbe6b3e..b17fad3b5 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -778,6 +778,8 @@ type FullNode interface { EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) //perm:read EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) //perm:read EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) //perm:read + EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) //perm:read + EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) //perm:read EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) //perm:read EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*EthTxReceipt, error) //perm:read EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) //perm:read diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 10ff250e6..b32fc7d8b 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1177,6 +1177,21 @@ func (mr *MockFullNodeMockRecorder) EthGetLogs(arg0, arg1 interface{}) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetLogs", reflect.TypeOf((*MockFullNode)(nil).EthGetLogs), arg0, arg1) } +// EthGetMessageCidByTransactionHash mocks base method. +func (m *MockFullNode) EthGetMessageCidByTransactionHash(arg0 context.Context, arg1 *ethtypes.EthHash) (*cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetMessageCidByTransactionHash", arg0, arg1) + ret0, _ := ret[0].(*cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetMessageCidByTransactionHash indicates an expected call of EthGetMessageCidByTransactionHash. +func (mr *MockFullNodeMockRecorder) EthGetMessageCidByTransactionHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetMessageCidByTransactionHash", reflect.TypeOf((*MockFullNode)(nil).EthGetMessageCidByTransactionHash), arg0, arg1) +} + // EthGetStorageAt mocks base method. func (m *MockFullNode) EthGetStorageAt(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 ethtypes.EthBytes, arg3 string) (ethtypes.EthBytes, error) { m.ctrl.T.Helper() @@ -1252,6 +1267,21 @@ func (mr *MockFullNodeMockRecorder) EthGetTransactionCount(arg0, arg1, arg2 inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionCount", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionCount), arg0, arg1, arg2) } +// EthGetTransactionHashByCid mocks base method. +func (m *MockFullNode) EthGetTransactionHashByCid(arg0 context.Context, arg1 cid.Cid) (*ethtypes.EthHash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionHashByCid", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthHash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionHashByCid indicates an expected call of EthGetTransactionHashByCid. +func (mr *MockFullNodeMockRecorder) EthGetTransactionHashByCid(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionHashByCid", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionHashByCid), arg0, arg1) +} + // EthGetTransactionReceipt mocks base method. func (m *MockFullNode) EthGetTransactionReceipt(arg0 context.Context, arg1 ethtypes.EthHash) (*api.EthTxReceipt, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 67249bb5f..aaa1d87c7 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -253,6 +253,8 @@ type FullNodeStruct struct { EthGetLogs func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) `perm:"read"` + EthGetMessageCidByTransactionHash func(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) `perm:"read"` + EthGetStorageAt func(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) `perm:"read"` EthGetTransactionByBlockHashAndIndex func(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) `perm:"read"` @@ -263,6 +265,8 @@ type FullNodeStruct struct { EthGetTransactionCount func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) `perm:"read"` + EthGetTransactionHashByCid func(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) `perm:"read"` + EthGetTransactionReceipt func(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) `perm:"read"` EthMaxPriorityFeePerGas func(p0 context.Context) (ethtypes.EthBigInt, error) `perm:"read"` @@ -2117,6 +2121,17 @@ func (s *FullNodeStub) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec return nil, ErrNotSupported } +func (s *FullNodeStruct) EthGetMessageCidByTransactionHash(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) { + if s.Internal.EthGetMessageCidByTransactionHash == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetMessageCidByTransactionHash(p0, p1) +} + +func (s *FullNodeStub) EthGetMessageCidByTransactionHash(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) { + return nil, ErrNotSupported +} + func (s *FullNodeStruct) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { if s.Internal.EthGetStorageAt == nil { return *new(ethtypes.EthBytes), ErrNotSupported @@ -2172,6 +2187,17 @@ func (s *FullNodeStub) EthGetTransactionCount(p0 context.Context, p1 ethtypes.Et return *new(ethtypes.EthUint64), ErrNotSupported } +func (s *FullNodeStruct) EthGetTransactionHashByCid(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) { + if s.Internal.EthGetTransactionHashByCid == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionHashByCid(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionHashByCid(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) { + return nil, ErrNotSupported +} + func (s *FullNodeStruct) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { if s.Internal.EthGetTransactionReceipt == nil { return nil, ErrNotSupported diff --git a/build/actors/pack.sh b/build/actors/pack.sh index 2702c80d5..c2060e67c 100755 --- a/build/actors/pack.sh +++ b/build/actors/pack.sh @@ -1,6 +1,6 @@ #!/bin/bash -NETWORKS=(devnet mainnet caterpillarnet butterflynet testing testing-fake-proofs calibrationnet hyperspace) +NETWORKS=(devnet mainnet caterpillarnet butterflynet testing testing-fake-proofs calibrationnet) set -e diff --git a/build/actors/v10.tar.zst b/build/actors/v10.tar.zst index f12e7b251..b0c9e5ce8 100644 Binary files a/build/actors/v10.tar.zst and b/build/actors/v10.tar.zst differ diff --git a/build/bootstrap/wallabynet.pi b/build/bootstrap/wallabynet.pi deleted file mode 100644 index 322e550bb..000000000 --- a/build/bootstrap/wallabynet.pi +++ /dev/null @@ -1,4 +0,0 @@ -/dns4/de0.bootstrap.wallaby.network/tcp/1337/p2p/12D3KooWHAvUVk5XuxSwi2dNLWbTDDRSGeHxMuWdQ3SQpRuNHbLz -/dns4/de1.bootstrap.wallaby.network/tcp/1337/p2p/12D3KooWBRqtxhJCtiLmCwKgAQozJtdGinEDdJGoS5oHw7vCjMGc -/dns4/ca0.bootstrap.wallaby.network/tcp/1337/p2p/12D3KooWCApBpUk7EX9pmEfyky1gKC6N2KJ74S1AwFfvnkDqw3pK -/dns4/sg0.bootstrap.wallaby.network/tcp/1337/p2p/12D3KooWLnYqr4hRoNHBJQVXsFGkDoKuoVfw5R2ASw1bHzrWU5Px \ No newline at end of file diff --git a/build/builtin_actors_gen.go b/build/builtin_actors_gen.go index 3ae845c16..38d4c49ed 100644 --- a/build/builtin_actors_gen.go +++ b/build/builtin_actors_gen.go @@ -53,14 +53,14 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "butterflynet", Version: 10, - ManifestCid: MustParseCid("bafy2bzacecjs7xvhtejsh47b2tx2iwe7mbad4kxovbfs7a6wxfl47kcnl25bm"), + ManifestCid: MustParseCid("bafy2bzaced2wq4k4i2deknam6ehbynaoo37bhysud7eze7su3ftlaggwwjuje"), Actors: map[string]cid.Cid{ "account": MustParseCid("bafk2bzacebd5zetyjtragjwrv2nqktct6u2pmsi4eifbanovxohx3a7lszjxi"), "cron": MustParseCid("bafk2bzacecrszortqkc7har77ssgajglymv6ftrqvmdko5h2yqqh5k2qospl2"), "datacap": MustParseCid("bafk2bzacecapjnxnyw4talwqv5ajbtbkzmzqiosztj5cb3sortyp73ndjl76e"), - "eam": MustParseCid("bafk2bzaceavdyeveel5iohjg7t6twc2cbdo7bt3m5xajwtibekudyhzv2xojy"), + "eam": MustParseCid("bafk2bzacebsvtqzp7g7vpufbyqrwwcpuo2yu3y7kenm7auidyiwzcv6jdw724"), "ethaccount": MustParseCid("bafk2bzacedl4pmkfxkzoqajs6im3ranmopozsmxjcxsnk3kwvd3vv7mfwwrf4"), - "evm": MustParseCid("bafk2bzacebgzvmvwv7rsnnhp3zhqbiqkumvyrc7pazfovpptgpgtqkalrli74"), + "evm": MustParseCid("bafk2bzacedx5wdyaihi22pwqqqtfxmuwh5acem46mzaep3znmhh5bsuqmxogq"), "init": MustParseCid("bafk2bzacecbxp66q3ytjkg37nyv4rmzezbfaigvx4i5yhvqbm5gg4amjeaias"), "multisig": MustParseCid("bafk2bzacecjltag3mn75dsnmrmopjow27buxqhabissowayqlmavrcfetqswc"), "paymentchannel": MustParseCid("bafk2bzacednzxg263eqbl2imwz3uhujov63tjkffieyl4hl3dhrgxyhwep6hc"), @@ -110,14 +110,14 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "calibrationnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaceaklxgrzd34i53rm4eeq6477nlq2ckpex27evftbsmjd2yrdbj4ba"), + ManifestCid: MustParseCid("bafy2bzacearpwvmcqlailxyq2d2wtzmtudxqhvfot77tbdqotek5qiq5hyhzg"), Actors: map[string]cid.Cid{ "account": MustParseCid("bafk2bzacea7zmrdz2rjbzlbmrmx3ko6pm3cbyqxxgogiqldsccbqffuok7m6s"), "cron": MustParseCid("bafk2bzacec7bxugi7ouh75nglycy7qwdq7e2hnku3w6yafq4fwdwvvq2mtrl2"), "datacap": MustParseCid("bafk2bzacedii4stmlo3ccdff7eevcolmgnuxy5ftkzbzwtkqa4iinlfzq4mei"), - "eam": MustParseCid("bafk2bzacea6du2tjdewnfd2zofjp342d2lw7rdl6hx4ejawup744kpym2xsf4"), + "eam": MustParseCid("bafk2bzacedykxiyewqijj5nksr7qi6o4wu5yz4rezb747ntql4rpidyfdpes4"), "ethaccount": MustParseCid("bafk2bzacecgbcbh3uk7olcfdqo44no5nxxayeqnycdznrlekqigbifor2revm"), - "evm": MustParseCid("bafk2bzaceanxhvz5czs6xfunhbysbttmim5e7poftibsu53uqn4by5nqmdaj6"), + "evm": MustParseCid("bafk2bzaceau5n66rabegik55kymni6uyk7n7jb5eymfywybs543yifpl7du2m"), "init": MustParseCid("bafk2bzacea7lxnvgxupwwgoxlmwtrca75w73qabe324wnwx43qranbgf5zdqo"), "multisig": MustParseCid("bafk2bzacear5eu5gpbjlroqkmsgpqerzc4aemp2uqcaeq7s2h4ur4ucgpzesg"), "paymentchannel": MustParseCid("bafk2bzacecwxuruxawcru7xfcx3rmt4hmhlfh4hi6jvfumerazz6jpvfmxxcw"), @@ -176,14 +176,14 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "caterpillarnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaceawh5opc4uqctlzc6xnq3pb7ycchfqwprjysbfa5xlrmiicbbvkrm"), + ManifestCid: MustParseCid("bafy2bzacebxr4uvnf5g3373shjzbaca6pf4th6nnfubytjfbrlxcpvbjw4ane"), Actors: map[string]cid.Cid{ "account": MustParseCid("bafk2bzacedfms6w3ghqtljpgsfuiqa6ztjx7kcuin6myjezj6rypj3zjbqms6"), "cron": MustParseCid("bafk2bzaceaganmlpozvy4jywigs46pfrtdmhjjey6uyhpurplqbasojsislba"), "datacap": MustParseCid("bafk2bzacebafqqe3wv5ytkfwmqzbmchgem66pw6yq6rl7w6vlhqsbkxnisswq"), - "eam": MustParseCid("bafk2bzaceawl3twv7iontkiiwgezkub2vvgd7cprhv7wvgpqjpeh4o6ygshlg"), + "eam": MustParseCid("bafk2bzacedwk5eqczflcsuisqsyeomgkpg54olojjq2ieb2ozu5s45wfwluti"), "ethaccount": MustParseCid("bafk2bzaceburkmtd63nmzxpux5rcxsbqr6x5didl2ce7al32g4tqrvo4pjz2i"), - "evm": MustParseCid("bafk2bzacea7tp4lop7ivhay3ozitkmxxurk74v4zse42ant47rh2uw5z3tq5e"), + "evm": MustParseCid("bafk2bzacedbroioygjnbjtc7ykcjjs4wfbwnaa6gkzubi7c5enifoqqqu66s6"), "init": MustParseCid("bafk2bzaced23r54kwuebl7t6mdantbby5qpfduxwxfryeliof2enyqzhokix6"), "multisig": MustParseCid("bafk2bzacebcn3rib6j6jvclys7dkf62hco45ssgamczkrtzt6xyewd6gt3mtu"), "paymentchannel": MustParseCid("bafk2bzacecvas4leo44pqdguj22nnwqoqdgwajzrpm5d6ltkehc37ni6p6doq"), @@ -233,14 +233,14 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "devnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzacedfwwsn5weycwkqrnusc37m6ut2uf42z5qvbukl67wi76mqtgafw2"), + ManifestCid: MustParseCid("bafy2bzacebixrjysarwxdadewlllfp4rwfoejxstwdutghghei54uvuuxlsbq"), Actors: map[string]cid.Cid{ "account": MustParseCid("bafk2bzacebb5txxkfexeaxa2th3rckxsxchzyss3ijgqbicf265h7rre2rvhm"), "cron": MustParseCid("bafk2bzacecotn4gwluhamoqwnzgbg7ogehv26o5xnhjzltnzfv6utrlyanzek"), "datacap": MustParseCid("bafk2bzacea4hket2srrtbewkf3tip6ellwpxdfbrzt5u47y57i2k6iojqqgba"), - "eam": MustParseCid("bafk2bzacecrg5sjpnmk3nu3vqyegkmjnvsjoumptseuu7zabeggu745bd2kwo"), + "eam": MustParseCid("bafk2bzacecxm2gr6tevzzan6oqp6aiqydjm5b7eo34mlzo5jdm7mnlbbueikq"), "ethaccount": MustParseCid("bafk2bzacedh4y3zvtgft3i6ift4rpptgr2dx67pvenowvq7yaspuf25gqgcdc"), - "evm": MustParseCid("bafk2bzacecrjgqoozymyoplrmtpi7bmkmggiqgpbgwkzooy2a67fjivuedm6a"), + "evm": MustParseCid("bafk2bzacec26myls7vg6anr5yjbb2r75dryhdzwlwnrhjcyuhahlaoxdrua6i"), "init": MustParseCid("bafk2bzacedof2ckc6w2qboxzxv4w67njcug4ut4cq3nnlrfybzsvlgnp4kt24"), "multisig": MustParseCid("bafk2bzacec4eqajjqhl53tnkbs7glu7njlbtlditi7lxhvw33ezmxk6jae46y"), "paymentchannel": MustParseCid("bafk2bzacec6nvdprqja7dy3qp5islebbbh2ifiyg2p7arbe6pocjhfe6xwkfy"), @@ -299,14 +299,14 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "hyperspace", Version: 10, - ManifestCid: MustParseCid("bafy2bzacedimb4dzty5tsyy3ucbcxai7crli452wn5cguhpmuelq74i4bffoo"), + ManifestCid: MustParseCid("bafy2bzaced6hc7ujjmypg6mkrxdmf32oh2udhmhpmwkqyxusdkxoi2uoodyxg"), Actors: map[string]cid.Cid{ "account": MustParseCid("bafk2bzacecim7uybic2qprbkjhowg7qkniv4zywj5h5g4u4ss72urco2akzuo"), "cron": MustParseCid("bafk2bzaceahgq64awp4f7li3hdgimc4upqvdvltpmeywckvens33umcxt424a"), "datacap": MustParseCid("bafk2bzacebkxn52ttooaslkwncijk3bgd3tm2zw7vijdhwvg2cxnxbrzmmq5e"), - "eam": MustParseCid("bafk2bzacedg5bnw3ic2ub4mb2agrvdowpqd7xyjv6v2ndlkugnrtjgzzfxqlw"), + "eam": MustParseCid("bafk2bzaceaftiqwpx6dcjfqxyq7pazn2p55diukf32pz74755vj7pgg5joexw"), "ethaccount": MustParseCid("bafk2bzacealn5enbxyxbfs7gbsjbyma2zk3bcr7okvflxhpr753d4eh6ixooa"), - "evm": MustParseCid("bafk2bzacedljkrmazyewawpnddrkzrt55556374dw2pm2hokgkompgzw4vx5y"), + "evm": MustParseCid("bafk2bzacea6etsvrqejjl7uej5dxlswja5gxzqyggsjjvg27timvtiedf7nsg"), "init": MustParseCid("bafk2bzacec55gyyaqjrw7zughywocgwcjvv6k5fijjpjw4xgckuqz6pjtff5a"), "multisig": MustParseCid("bafk2bzaceblozbdzybdivvjdiid4jwm2jc6x5a66sunh2vvwsqba6wzqmr7i6"), "paymentchannel": MustParseCid("bafk2bzacealcyke5a6n24efs6qe4iikynpk2twqssyugy7jcyf6p6shgw2iwa"), @@ -356,14 +356,14 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "mainnet", Version: 10, - ManifestCid: MustParseCid("bafy2bzaceat4ut5xv3qn4lvvkvwvdn6gtlbnqzvueh67fjqlemw6eled5oqnc"), + ManifestCid: MustParseCid("bafy2bzacea5vylkbby7rb42fknkk4g4byhj7hkqlxp4z4urydi3vlpwsgllik"), Actors: map[string]cid.Cid{ "account": MustParseCid("bafk2bzacedsn6i2flkpk6sb4iuejo7gfl5n6fhsdawggtbsihlrrjtvs7oepu"), "cron": MustParseCid("bafk2bzacecw4guere7ba2canyi2622lw52b5qbn7iubckcp5cwlmx2kw7qqwy"), "datacap": MustParseCid("bafk2bzaceat2ncckd2jjjqcovd3ib4sylwff7jk7rlk6gr5d2gmrrc7isrmu2"), - "eam": MustParseCid("bafk2bzacebl7267zqf7aubpl7n6ligulayhz65dpgn3ii26b3wwjwytlsdc3i"), + "eam": MustParseCid("bafk2bzacebbpu5smjrjqpkrvvlhcpk23yvlovlndqmwzhfz5kuuph54tdw732"), "ethaccount": MustParseCid("bafk2bzacedmwzkbytxfn7exmxxosomvix4mpyxrmupeqw45aofqmdq5q7mgqe"), - "evm": MustParseCid("bafk2bzacecrrwixyqwphxaybhy5zxuawkhncq5tgtuz2ind4bl22oivzjidoq"), + "evm": MustParseCid("bafk2bzacechkf43lmddynxmc35hvz5kwr3fdxrbg6fxbcvysfsihgiopbrb7o"), "init": MustParseCid("bafk2bzacec6276d7ls3hhuqibqorn3yp45mv7hroczf3bgb6jkhmbb2zqt3bw"), "multisig": MustParseCid("bafk2bzaceahggxrnjj3w3cgtko54srssqyhcs4x6y55ytego6jf2owg5piw3y"), "paymentchannel": MustParseCid("bafk2bzaceaobaqjamso57bkjv3n4ilv7lfropgrncnnej666w3tegmr4cfgve"), @@ -413,14 +413,14 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "testing", Version: 10, - ManifestCid: MustParseCid("bafy2bzacearh2dy6uzcfvruckai5qbf7banklkg6uezaa7w6onsmdfxn2qxbs"), + ManifestCid: MustParseCid("bafy2bzacea7tbn4p232ecrjvlp2uvpci5pexqjqq2vpv4t5ihktpja2zsj3ek"), Actors: map[string]cid.Cid{ "account": MustParseCid("bafk2bzaceds3iy5qjgr3stoywxt4uxvhybca23q7d2kxhitedgudrkhxaxa6o"), "cron": MustParseCid("bafk2bzacebxp4whb4ocqxnbvqlz3kckarabtyvhjbhqvrdwhejuffwactyiss"), "datacap": MustParseCid("bafk2bzacedepm3zas6vqryruwiz7d3axkneo7v66q65gf2dlpfd53pjlycrg4"), - "eam": MustParseCid("bafk2bzacedl4q6l3m5uvunuviwlxicyweszrahipxfpf3nt6wspvcdi4ryzyk"), + "eam": MustParseCid("bafk2bzacea2uascrtv6xnsqlxyf3tcf4onpgrs7frh55p6dnrdeum2uup7wx4"), "ethaccount": MustParseCid("bafk2bzacecbhz4ipg773lsovgpjysm6fxl2i7y2wuxadqnt4s4vm3nd2qodb4"), - "evm": MustParseCid("bafk2bzaced5efc2bi7ulqsep4ej74hxwbjap2qi7lojiqzfsowxr4kylkwzk6"), + "evm": MustParseCid("bafk2bzaceabwn4i62od3i4qkuj5zx4vn5w5cbcl53tqnszk6kl43bfl55hl6c"), "init": MustParseCid("bafk2bzacebqym5i5eciyyyzsimu73z6bkffpm5hzjpx3gwcm64pm2fh7okrja"), "multisig": MustParseCid("bafk2bzacecmlyngek7qvj5ezaaitadrycapup3mbty4ijlzun6g23tcoysxle"), "paymentchannel": MustParseCid("bafk2bzacedspin4hxpgnxkjen3hsxpcc52oc5q4ypukl4qq6vaytcgmmi7hl4"), @@ -470,14 +470,14 @@ var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMet }, { Network: "testing-fake-proofs", Version: 10, - ManifestCid: MustParseCid("bafy2bzacedw6kk3u2vjexzqmm3vtvusd42lllk7wbnsrlxxmt35smsnmiatca"), + ManifestCid: MustParseCid("bafy2bzacecyqfyzmw72234rvbk6vzq2omnmt3cbfezkq2h3ewnn33w42b2s62"), Actors: map[string]cid.Cid{ "account": MustParseCid("bafk2bzaceds3iy5qjgr3stoywxt4uxvhybca23q7d2kxhitedgudrkhxaxa6o"), "cron": MustParseCid("bafk2bzacebxp4whb4ocqxnbvqlz3kckarabtyvhjbhqvrdwhejuffwactyiss"), "datacap": MustParseCid("bafk2bzacedepm3zas6vqryruwiz7d3axkneo7v66q65gf2dlpfd53pjlycrg4"), - "eam": MustParseCid("bafk2bzacedl4q6l3m5uvunuviwlxicyweszrahipxfpf3nt6wspvcdi4ryzyk"), + "eam": MustParseCid("bafk2bzacea2uascrtv6xnsqlxyf3tcf4onpgrs7frh55p6dnrdeum2uup7wx4"), "ethaccount": MustParseCid("bafk2bzacecbhz4ipg773lsovgpjysm6fxl2i7y2wuxadqnt4s4vm3nd2qodb4"), - "evm": MustParseCid("bafk2bzaced5efc2bi7ulqsep4ej74hxwbjap2qi7lojiqzfsowxr4kylkwzk6"), + "evm": MustParseCid("bafk2bzaceabwn4i62od3i4qkuj5zx4vn5w5cbcl53tqnszk6kl43bfl55hl6c"), "init": MustParseCid("bafk2bzacebqym5i5eciyyyzsimu73z6bkffpm5hzjpx3gwcm64pm2fh7okrja"), "multisig": MustParseCid("bafk2bzacecmlyngek7qvj5ezaaitadrycapup3mbty4ijlzun6g23tcoysxle"), "paymentchannel": MustParseCid("bafk2bzacedspin4hxpgnxkjen3hsxpcc52oc5q4ypukl4qq6vaytcgmmi7hl4"), diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 6b4fc7b4f..d7a354461 100644 Binary files a/build/openrpc/full.json.gz and b/build/openrpc/full.json.gz differ diff --git a/build/openrpc/gateway.json.gz b/build/openrpc/gateway.json.gz index cc3870a49..74a9f3221 100644 Binary files a/build/openrpc/gateway.json.gz and b/build/openrpc/gateway.json.gz differ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 4f67b5393..d3750150c 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 88e449804..e84f7f5d1 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/build/params_hyperspace.go b/build/params_hyperspace.go deleted file mode 100644 index 1e75c6bfa..000000000 --- a/build/params_hyperspace.go +++ /dev/null @@ -1,94 +0,0 @@ -//go:build hyperspacenet -// +build hyperspacenet - -package build - -import ( - "github.com/ipfs/go-cid" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - actorstypes "github.com/filecoin-project/go-state-types/actors" - "github.com/filecoin-project/go-state-types/network" - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" - - "github.com/filecoin-project/lotus/chain/actors/policy" -) - -var NetworkBundle = "hyperspace" -var BundleOverrides map[actorstypes.Version]string -var ActorDebugging = false - -const BootstrappersFile = "hyperspacenet.pi" -const GenesisFile = "hyperspacenet.car" - -const GenesisNetworkVersion = network.Version18 - -var UpgradeBreezeHeight = abi.ChainEpoch(-1) - -const BreezeGasTampingDuration = 120 - -var UpgradeSmokeHeight = abi.ChainEpoch(-1) -var UpgradeIgnitionHeight = abi.ChainEpoch(-2) -var UpgradeRefuelHeight = abi.ChainEpoch(-3) -var UpgradeTapeHeight = abi.ChainEpoch(-4) - -var UpgradeAssemblyHeight = abi.ChainEpoch(-5) -var UpgradeLiftoffHeight = abi.ChainEpoch(-6) - -var UpgradeKumquatHeight = abi.ChainEpoch(-7) -var UpgradeCalicoHeight = abi.ChainEpoch(-9) -var UpgradePersianHeight = abi.ChainEpoch(-10) -var UpgradeOrangeHeight = abi.ChainEpoch(-11) -var UpgradeClausHeight = abi.ChainEpoch(-12) - -var UpgradeTrustHeight = abi.ChainEpoch(-13) - -var UpgradeNorwegianHeight = abi.ChainEpoch(-14) - -var UpgradeTurboHeight = abi.ChainEpoch(-15) - -var UpgradeHyperdriveHeight = abi.ChainEpoch(-16) -var UpgradeChocolateHeight = abi.ChainEpoch(-17) -var UpgradeOhSnapHeight = abi.ChainEpoch(-18) -var UpgradeSkyrHeight = abi.ChainEpoch(-19) -var UpgradeSharkHeight = abi.ChainEpoch(-20) -var UpgradeHyggeHeight = abi.ChainEpoch(-21) - -var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ - 0: DrandMainnet, -} - -var SupportedProofTypes = []abi.RegisteredSealProof{ - abi.RegisteredSealProof_StackedDrg512MiBV1, - abi.RegisteredSealProof_StackedDrg32GiBV1, - abi.RegisteredSealProof_StackedDrg64GiBV1, -} -var ConsensusMinerMinPower = abi.NewStoragePower(16 << 30) -var MinVerifiedDealSize = abi.NewStoragePower(1 << 20) -var PreCommitChallengeDelay = abi.ChainEpoch(10) - -func init() { - policy.SetSupportedProofTypes(SupportedProofTypes...) - policy.SetConsensusMinerMinPower(ConsensusMinerMinPower) - policy.SetMinVerifiedDealSize(MinVerifiedDealSize) - policy.SetPreCommitChallengeDelay(PreCommitChallengeDelay) - - BuildType = BuildHyperspacenet - SetAddressNetwork(address.Testnet) - Devnet = true - -} - -const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) - -const PropagationDelaySecs = uint64(6) - -// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start -const BootstrapPeerThreshold = 2 - -// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. -// As per https://github.com/ethereum-lists/chains -const Eip155ChainId = 3141 - -var WhitelistedBlock = cid.Undef diff --git a/build/params_mainnet.go b/build/params_mainnet.go index f95525dbc..e29fa4567 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -1,5 +1,5 @@ -//go:build !debug && !2k && !testground && !calibnet && !butterflynet && !interopnet && !wallabynet && !hyperspacenet -// +build !debug,!2k,!testground,!calibnet,!butterflynet,!interopnet,!wallabynet,!hyperspacenet +//go:build !debug && !2k && !testground && !calibnet && !butterflynet && !interopnet +// +build !debug,!2k,!testground,!calibnet,!butterflynet,!interopnet package build diff --git a/build/params_testground.go b/build/params_testground.go index ed818f4a5..17ea5a59b 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -42,9 +42,6 @@ var ( AllowableClockDriftSecs = uint64(1) - Finality = policy.ChainFinality - ForkLengthThreshold = Finality - SlashablePowerDelay = 20 InteractivePoRepConfidence = 6 @@ -130,6 +127,9 @@ var ( GenesisFile = "" ) +const Finality = policy.ChainFinality +const ForkLengthThreshold = Finality + const BootstrapPeerThreshold = 1 // ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. diff --git a/build/params_wallaby.go b/build/params_wallaby.go deleted file mode 100644 index c5279bfd0..000000000 --- a/build/params_wallaby.go +++ /dev/null @@ -1,94 +0,0 @@ -//go:build wallabynet -// +build wallabynet - -package build - -import ( - "github.com/ipfs/go-cid" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - actorstypes "github.com/filecoin-project/go-state-types/actors" - "github.com/filecoin-project/go-state-types/network" - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" - - "github.com/filecoin-project/lotus/chain/actors/policy" -) - -var NetworkBundle = "wallaby" -var BundleOverrides map[actorstypes.Version]string -var ActorDebugging = false - -const BootstrappersFile = "wallabynet.pi" -const GenesisFile = "wallabynet.car" - -const GenesisNetworkVersion = network.Version18 - -var UpgradeBreezeHeight = abi.ChainEpoch(-1) - -const BreezeGasTampingDuration = 120 - -var UpgradeSmokeHeight = abi.ChainEpoch(-1) -var UpgradeIgnitionHeight = abi.ChainEpoch(-2) -var UpgradeRefuelHeight = abi.ChainEpoch(-3) -var UpgradeTapeHeight = abi.ChainEpoch(-4) - -var UpgradeAssemblyHeight = abi.ChainEpoch(-5) -var UpgradeLiftoffHeight = abi.ChainEpoch(-6) - -var UpgradeKumquatHeight = abi.ChainEpoch(-7) -var UpgradeCalicoHeight = abi.ChainEpoch(-9) -var UpgradePersianHeight = abi.ChainEpoch(-10) -var UpgradeOrangeHeight = abi.ChainEpoch(-11) -var UpgradeClausHeight = abi.ChainEpoch(-12) - -var UpgradeTrustHeight = abi.ChainEpoch(-13) - -var UpgradeNorwegianHeight = abi.ChainEpoch(-14) - -var UpgradeTurboHeight = abi.ChainEpoch(-15) - -var UpgradeHyperdriveHeight = abi.ChainEpoch(-16) -var UpgradeChocolateHeight = abi.ChainEpoch(-17) -var UpgradeOhSnapHeight = abi.ChainEpoch(-18) -var UpgradeSkyrHeight = abi.ChainEpoch(-19) -var UpgradeSharkHeight = abi.ChainEpoch(-20) -var UpgradeHyggeHeight = abi.ChainEpoch(-21) - -var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ - 0: DrandMainnet, -} - -var SupportedProofTypes = []abi.RegisteredSealProof{ - abi.RegisteredSealProof_StackedDrg512MiBV1, - abi.RegisteredSealProof_StackedDrg32GiBV1, - abi.RegisteredSealProof_StackedDrg64GiBV1, -} -var ConsensusMinerMinPower = abi.NewStoragePower(16 << 30) -var MinVerifiedDealSize = abi.NewStoragePower(1 << 20) -var PreCommitChallengeDelay = abi.ChainEpoch(10) - -func init() { - policy.SetSupportedProofTypes(SupportedProofTypes...) - policy.SetConsensusMinerMinPower(ConsensusMinerMinPower) - policy.SetMinVerifiedDealSize(MinVerifiedDealSize) - policy.SetPreCommitChallengeDelay(PreCommitChallengeDelay) - - BuildType = BuildWallabynet - SetAddressNetwork(address.Testnet) - Devnet = true - -} - -const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) - -const PropagationDelaySecs = uint64(6) - -// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start -const BootstrapPeerThreshold = 2 - -// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. -// As per https://github.com/ethereum-lists/chains -const Eip155ChainId = 31415 - -var WhitelistedBlock = cid.Undef diff --git a/build/version.go b/build/version.go index 4946059e2..70e27ad50 100644 --- a/build/version.go +++ b/build/version.go @@ -6,15 +6,13 @@ var CurrentCommit string var BuildType int const ( - BuildDefault = 0 - BuildMainnet = 0x1 - Build2k = 0x2 - BuildDebug = 0x3 - BuildCalibnet = 0x4 - BuildInteropnet = 0x5 - BuildButterflynet = 0x7 - BuildWallabynet = 0x8 - BuildHyperspacenet = 0x9 + BuildDefault = 0 + BuildMainnet = 0x1 + Build2k = 0x2 + BuildDebug = 0x3 + BuildCalibnet = 0x4 + BuildInteropnet = 0x5 + BuildButterflynet = 0x7 ) func BuildTypeString() string { @@ -33,10 +31,6 @@ func BuildTypeString() string { return "+interopnet" case BuildButterflynet: return "+butterflynet" - case BuildWallabynet: - return "+wallabynet" - case BuildHyperspacenet: - return "+hyperspacenet" default: return "+huh?" } diff --git a/chain/ethhashlookup/eth_transaction_hash_lookup.go b/chain/ethhashlookup/eth_transaction_hash_lookup.go new file mode 100644 index 000000000..85cb84db1 --- /dev/null +++ b/chain/ethhashlookup/eth_transaction_hash_lookup.go @@ -0,0 +1,163 @@ +package ethhashlookup + +import ( + "database/sql" + "errors" + "strconv" + + "github.com/ipfs/go-cid" + _ "github.com/mattn/go-sqlite3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types/ethtypes" +) + +var ErrNotFound = errors.New("not found") + +var pragmas = []string{ + "PRAGMA synchronous = normal", + "PRAGMA temp_store = memory", + "PRAGMA mmap_size = 30000000000", + "PRAGMA page_size = 32768", + "PRAGMA auto_vacuum = NONE", + "PRAGMA automatic_index = OFF", + "PRAGMA journal_mode = WAL", + "PRAGMA read_uncommitted = ON", +} + +var ddls = []string{ + `CREATE TABLE IF NOT EXISTS eth_tx_hashes ( + hash TEXT PRIMARY KEY NOT NULL, + cid TEXT NOT NULL UNIQUE, + insertion_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + )`, + + `CREATE INDEX IF NOT EXISTS insertion_time_index ON eth_tx_hashes (insertion_time)`, + + // metadata containing version of schema + `CREATE TABLE IF NOT EXISTS _meta ( + version UINT64 NOT NULL UNIQUE + )`, + + // version 1. + `INSERT OR IGNORE INTO _meta (version) VALUES (1)`, +} + +const schemaVersion = 1 + +const ( + insertTxHash = `INSERT INTO eth_tx_hashes + (hash, cid) + VALUES(?, ?) + ON CONFLICT (hash) DO UPDATE SET insertion_time = CURRENT_TIMESTAMP` +) + +type EthTxHashLookup struct { + db *sql.DB +} + +func (ei *EthTxHashLookup) UpsertHash(txHash ethtypes.EthHash, c cid.Cid) error { + hashEntry, err := ei.db.Prepare(insertTxHash) + if err != nil { + return xerrors.Errorf("prepare insert event: %w", err) + } + + _, err = hashEntry.Exec(txHash.String(), c.String()) + return err +} + +func (ei *EthTxHashLookup) GetCidFromHash(txHash ethtypes.EthHash) (cid.Cid, error) { + q, err := ei.db.Query("SELECT cid FROM eth_tx_hashes WHERE hash = :hash;", sql.Named("hash", txHash.String())) + if err != nil { + return cid.Undef, err + } + + var c string + if !q.Next() { + return cid.Undef, ErrNotFound + } + err = q.Scan(&c) + if err != nil { + return cid.Undef, err + } + return cid.Decode(c) +} + +func (ei *EthTxHashLookup) GetHashFromCid(c cid.Cid) (ethtypes.EthHash, error) { + q, err := ei.db.Query("SELECT hash FROM eth_tx_hashes WHERE cid = :cid;", sql.Named("cid", c.String())) + if err != nil { + return ethtypes.EmptyEthHash, err + } + + var hashString string + if !q.Next() { + return ethtypes.EmptyEthHash, ErrNotFound + } + err = q.Scan(&hashString) + if err != nil { + return ethtypes.EmptyEthHash, err + } + return ethtypes.ParseEthHash(hashString) +} + +func (ei *EthTxHashLookup) DeleteEntriesOlderThan(days int) (int64, error) { + res, err := ei.db.Exec("DELETE FROM eth_tx_hashes WHERE insertion_time < datetime('now', ?);", "-"+strconv.Itoa(days)+" day") + if err != nil { + return 0, err + } + + return res.RowsAffected() +} + +func NewTransactionHashLookup(path string) (*EthTxHashLookup, error) { + db, err := sql.Open("sqlite3", path+"?mode=rwc") + if err != nil { + return nil, xerrors.Errorf("open sqlite3 database: %w", err) + } + + for _, pragma := range pragmas { + if _, err := db.Exec(pragma); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec pragma %q: %w", pragma, err) + } + } + + q, err := db.Query("SELECT name FROM sqlite_master WHERE type='table' AND name='_meta';") + if err == sql.ErrNoRows || !q.Next() { + // empty database, create the schema + for _, ddl := range ddls { + if _, err := db.Exec(ddl); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec ddl %q: %w", ddl, err) + } + } + } else if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("looking for _meta table: %w", err) + } else { + // Ensure we don't open a database from a different schema version + + row := db.QueryRow("SELECT max(version) FROM _meta") + var version int + err := row.Scan(&version) + if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: no version found") + } + if version != schemaVersion { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: got %d, expected %d", version, schemaVersion) + } + } + + return &EthTxHashLookup{ + db: db, + }, nil +} + +func (ei *EthTxHashLookup) Close() error { + if ei.db == nil { + return nil + } + return ei.db.Close() +} diff --git a/chain/events/filter/event.go b/chain/events/filter/event.go index a19f49a50..7d59ad8bd 100644 --- a/chain/events/filter/event.go +++ b/chain/events/filter/event.go @@ -100,9 +100,18 @@ func (f *EventFilter) CollectEvents(ctx context.Context, te *TipSetEvents, rever continue } + decodedEntries := make([]types.EventEntry, len(ev.Entries)) + for i, entry := range ev.Entries { + decodedEntries[i] = types.EventEntry{ + Flags: entry.Flags, + Key: entry.Key, + Value: decodeLogBytes(entry.Value), + } + } + // event matches filter, so record it cev := &CollectedEvent{ - Entries: ev.Entries, + Entries: decodedEntries, EmitterAddr: addr, EventIdx: evIdx, Reverted: revert, @@ -266,13 +275,13 @@ func (te *TipSetEvents) messages(ctx context.Context) ([]executedMessage, error) } type executedMessage struct { - msg *types.Message + msg types.ChainMsg rct *types.MessageReceipt // events extracted from receipt evs []*types.Event } -func (e *executedMessage) Message() *types.Message { +func (e *executedMessage) Message() types.ChainMsg { return e.msg } @@ -428,7 +437,7 @@ func (m *EventFilterManager) loadExecutedMessages(ctx context.Context, msgTs, rc ems := make([]executedMessage, len(msgs)) for i := 0; i < len(msgs); i++ { - ems[i].msg = msgs[i].VMMessage() + ems[i].msg = msgs[i] var rct types.MessageReceipt found, err := arr.Get(uint64(i), &rct) diff --git a/chain/events/filter/index.go b/chain/events/filter/index.go index 45cabaa11..1920a91fe 100644 --- a/chain/events/filter/index.go +++ b/chain/events/filter/index.go @@ -225,7 +225,7 @@ func (ei *EventIndex) CollectEvents(ctx context.Context, te *TipSetEvents, rever // This function swallows errors and returns the original array if it failed // to decode. func decodeLogBytes(orig []byte) []byte { - if orig == nil { + if len(orig) == 0 { return orig } decoded, err := cbg.ReadByteArray(bytes.NewReader(orig), uint64(len(orig))) diff --git a/chain/events/filter/mempool.go b/chain/events/filter/mempool.go index 250fc5961..39ccf12c2 100644 --- a/chain/events/filter/mempool.go +++ b/chain/events/filter/mempool.go @@ -5,7 +5,6 @@ import ( "sync" "time" - "github.com/ipfs/go-cid" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/api" @@ -18,7 +17,7 @@ type MemPoolFilter struct { ch chan<- interface{} mu sync.Mutex - collected []cid.Cid + collected []*types.SignedMessage lastTaken time.Time } @@ -55,10 +54,10 @@ func (f *MemPoolFilter) CollectMessage(ctx context.Context, msg *types.SignedMes copy(f.collected, f.collected[1:]) f.collected = f.collected[:len(f.collected)-1] } - f.collected = append(f.collected, msg.Cid()) + f.collected = append(f.collected, msg) } -func (f *MemPoolFilter) TakeCollectedMessages(context.Context) []cid.Cid { +func (f *MemPoolFilter) TakeCollectedMessages(context.Context) []*types.SignedMessage { f.mu.Lock() collected := f.collected f.collected = nil diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go index 96457e9f8..6a622dd57 100644 --- a/chain/messagesigner/messagesigner.go +++ b/chain/messagesigner/messagesigner.go @@ -13,10 +13,13 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/chain/wallet/key" "github.com/filecoin-project/lotus/node/modules/dtypes" ) @@ -66,15 +69,24 @@ func (ms *MessageSigner) SignMessage(ctx context.Context, msg *types.Message, sp // Sign the message with the nonce msg.Nonce = nonce + keyInfo, err := ms.wallet.WalletExport(ctx, msg.From) + if err != nil { + return nil, err + } + sb, err := SigningBytes(msg, key.ActSigType(keyInfo.Type)) + if err != nil { + return nil, err + } mb, err := msg.ToStorageBlock() if err != nil { return nil, xerrors.Errorf("serializing message: %w", err) } - sig, err := ms.wallet.WalletSign(ctx, msg.From, mb.Cid().Bytes(), api.MsgMeta{ + sig, err := ms.wallet.WalletSign(ctx, msg.From, sb, api.MsgMeta{ Type: api.MTChainMsg, Extra: mb.RawData(), }) + if err != nil { return nil, xerrors.Errorf("failed to sign message: %w, addr=%s", err, msg.From) } @@ -187,3 +199,19 @@ func (ms *MessageSigner) SaveNonce(ctx context.Context, addr address.Address, no func (ms *MessageSigner) dstoreKey(addr address.Address) datastore.Key { return datastore.KeyWithNamespaces([]string{dsKeyActorNonce, addr.String()}) } + +func SigningBytes(msg *types.Message, sigType crypto.SigType) ([]byte, error) { + if sigType == crypto.SigTypeDelegated { + txArgs, err := ethtypes.EthTxArgsFromMessage(msg) + if err != nil { + return nil, xerrors.Errorf("failed to reconstruct eth transaction: %w", err) + } + rlpEncodedMsg, err := txArgs.ToRlpUnsignedMsg() + if err != nil { + return nil, xerrors.Errorf("failed to repack eth rlp message: %w", err) + } + return rlpEncodedMsg, nil + } + + return msg.Cid().Bytes(), nil +} diff --git a/chain/store/snapshot.go b/chain/store/snapshot.go index f9e65f4bf..e90a60611 100644 --- a/chain/store/snapshot.go +++ b/chain/store/snapshot.go @@ -22,6 +22,8 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) +const TipsetkeyBackfillRange = 2 * build.Finality + func (cs *ChainStore) UnionStore() bstore.Blockstore { return bstore.Union(cs.stateBlockstore, cs.chainBlockstore) } @@ -113,6 +115,20 @@ func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, e return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) } + ts := root + for i := 0; i < int(TipsetkeyBackfillRange); i++ { + err = cs.PersistTipset(ctx, ts) + if err != nil { + return nil, err + } + parentTsKey := ts.Parents() + ts, err = cs.LoadTipSet(ctx, parentTsKey) + if ts == nil || err != nil { + log.Warnf("Only able to load the last %d tipsets", i) + break + } + } + return root, nil } diff --git a/chain/store/store.go b/chain/store/store.go index 9ab08c74f..6dc17d766 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -384,7 +384,19 @@ func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error { if err != nil { return xerrors.Errorf("errored while expanding tipset: %w", err) } - log.Debugf("expanded %s into %s\n", ts.Cids(), expanded.Cids()) + + if expanded.Key() != ts.Key() { + log.Debugf("expanded %s into %s\n", ts.Cids(), expanded.Cids()) + + tsBlk, err := expanded.Key().ToStorageBlock() + if err != nil { + return xerrors.Errorf("failed to get tipset key block: %w", err) + } + + if err = cs.chainLocalBlockstore.Put(ctx, tsBlk); err != nil { + return xerrors.Errorf("failed to put tipset key block: %w", err) + } + } if err := cs.MaybeTakeHeavierTipSet(ctx, expanded); err != nil { return xerrors.Errorf("MaybeTakeHeavierTipSet failed in PutTipSet: %w", err) @@ -1097,6 +1109,10 @@ func (cs *ChainStore) StateBlockstore() bstore.Blockstore { return cs.stateBlockstore } +func (cs *ChainStore) ChainLocalBlockstore() bstore.Blockstore { + return cs.chainLocalBlockstore +} + func ActorStore(ctx context.Context, bs bstore.Blockstore) adt.Store { return adt.WrapStore(ctx, cbor.NewCborStore(bs)) } diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 42a57ab7c..f5765fddc 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/ipfs/go-datastore" + "github.com/stretchr/testify/require" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" @@ -125,6 +126,51 @@ func TestChainExportImport(t *testing.T) { } } +// Test to check if tipset key cids are being stored on snapshot +func TestChainImportTipsetKeyCid(t *testing.T) { + + ctx := context.Background() + cg, err := gen.NewGenerator() + require.NoError(t, err) + + buf := new(bytes.Buffer) + var last *types.TipSet + var tsKeys []types.TipSetKey + for i := 0; i < 10; i++ { + ts, err := cg.NextTipSet() + require.NoError(t, err) + last = ts.TipSet.TipSet() + tsKeys = append(tsKeys, last.Key()) + } + + if err := cg.ChainStore().Export(ctx, last, last.Height(), false, buf); err != nil { + t.Fatal(err) + } + + nbs := blockstore.NewMemorySync() + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) + defer cs.Close() //nolint:errcheck + + root, err := cs.Import(ctx, buf) + require.NoError(t, err) + + require.Truef(t, root.Equals(last), "imported chain differed from exported chain") + + err = cs.SetHead(ctx, last) + require.NoError(t, err) + + for _, tsKey := range tsKeys { + _, err := cs.LoadTipSet(ctx, tsKey) + require.NoError(t, err) + + tsCid, err := tsKey.Cid() + require.NoError(t, err) + _, err = cs.ChainLocalBlockstore().Get(ctx, tsCid) + require.NoError(t, err) + + } +} + func TestChainExportImportFull(t *testing.T) { //stm: @CHAIN_GEN_NEXT_TIPSET_001 //stm: @CHAIN_STORE_IMPORT_001, @CHAIN_STORE_EXPORT_001, @CHAIN_STORE_SET_HEAD_001 diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index ba4b14f56..b6ae22169 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -231,6 +231,36 @@ func (tx *EthTxArgs) ToRlpUnsignedMsg() ([]byte, error) { return append([]byte{0x02}, encoded...), nil } +func (tx *EthTx) ToEthTxArgs() EthTxArgs { + return EthTxArgs{ + ChainID: int(tx.ChainID), + Nonce: int(tx.Nonce), + To: tx.To, + Value: big.Int(tx.Value), + MaxFeePerGas: big.Int(tx.MaxFeePerGas), + MaxPriorityFeePerGas: big.Int(tx.MaxPriorityFeePerGas), + GasLimit: int(tx.Gas), + Input: tx.Input, + V: big.Int(tx.V), + R: big.Int(tx.R), + S: big.Int(tx.S), + } +} + +func (tx *EthTx) TxHash() (EthHash, error) { + ethTxArgs := tx.ToEthTxArgs() + return (ðTxArgs).TxHash() +} + +func (tx *EthTxArgs) TxHash() (EthHash, error) { + rlp, err := tx.ToRlpSignedMsg() + if err != nil { + return EmptyEthHash, err + } + + return EthHashFromTxBytes(rlp), nil +} + func (tx *EthTxArgs) ToRlpSignedMsg() ([]byte, error) { packed1, err := tx.packTxFields() if err != nil { diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index a79238bf8..235cc7c79 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -36,10 +36,7 @@ var ErrInvalidAddress = errors.New("invalid Filecoin Eth address") type EthUint64 uint64 func (e EthUint64) MarshalJSON() ([]byte, error) { - if e == 0 { - return json.Marshal("0x0") - } - return json.Marshal(fmt.Sprintf("0x%x", e)) + return json.Marshal(e.Hex()) } func (e *EthUint64) UnmarshalJSON(b []byte) error { @@ -64,6 +61,13 @@ func EthUint64FromHex(s string) (EthUint64, error) { return EthUint64(parsedInt), nil } +func (e EthUint64) Hex() string { + if e == 0 { + return "0x0" + } + return fmt.Sprintf("0x%x", e) +} + // EthBigInt represents a large integer whose zero value serializes to "0x0". type EthBigInt big.Int @@ -360,14 +364,7 @@ func (h *EthHash) UnmarshalJSON(b []byte) error { } func decodeHexString(s string, expectedLen int) ([]byte, error) { - // Strip the leading 0x or 0X prefix since hex.DecodeString does not support it. - if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { - s = s[2:] - } - // Sometimes clients will omit a leading zero in a byte; pad so we can decode correctly. - if len(s)%2 == 1 { - s = "0" + s - } + s = handleHexStringPrefix(s) if len(s) != expectedLen*2 { return nil, xerrors.Errorf("expected hex string length sans prefix %d, got %d", expectedLen*2, len(s)) } @@ -378,6 +375,27 @@ func decodeHexString(s string, expectedLen int) ([]byte, error) { return b, nil } +func DecodeHexString(s string) ([]byte, error) { + s = handleHexStringPrefix(s) + b, err := hex.DecodeString(s) + if err != nil { + return nil, xerrors.Errorf("cannot parse hex value: %w", err) + } + return b, nil +} + +func handleHexStringPrefix(s string) string { + // Strip the leading 0x or 0X prefix since hex.DecodeString does not support it. + if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { + s = s[2:] + } + // Sometimes clients will omit a leading zero in a byte; pad so we can decode correctly. + if len(s)%2 == 1 { + s = "0" + s + } + return s +} + func EthHashFromCid(c cid.Cid) (EthHash, error) { return ParseEthHash(c.Hash().HexString()[8:]) } @@ -392,10 +410,21 @@ func ParseEthHash(s string) (EthHash, error) { return h, nil } +func EthHashFromTxBytes(b []byte) EthHash { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(b) + hash := hasher.Sum(nil) + + var ethHash EthHash + copy(ethHash[:], hash) + return ethHash +} + func (h EthHash) String() string { return "0x" + hex.EncodeToString(h[:]) } +// Should ONLY be used for blocks and Filecoin messages. Eth transactions expect a different hashing scheme. func (h EthHash) ToCid() cid.Cid { // err is always nil mh, _ := multihash.EncodeName(h[:], "blake2b-256") @@ -556,7 +585,7 @@ type EthLog struct { // The index corresponds to the sequence of messages produced by ChainGetParentMessages TransactionIndex EthUint64 `json:"transactionIndex"` - // TransactionHash is the cid of the message that produced the event log. + // TransactionHash is the hash of the RLP message that produced the event log. TransactionHash EthHash `json:"transactionHash"` // BlockHash is the hash of the tipset containing the message that produced the log. diff --git a/cli/evm.go b/cli/evm.go index 739f56f8b..1c02f92b4 100644 --- a/cli/evm.go +++ b/cli/evm.go @@ -38,25 +38,13 @@ var EvmCmd = &cli.Command{ } var EvmGetInfoCmd = &cli.Command{ - Name: "stat", - Usage: "Print eth/filecoin addrs and code cid", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "filAddr", - Usage: "Filecoin address", - }, - &cli.StringFlag{ - Name: "ethAddr", - Usage: "Ethereum address", - }, - }, + Name: "stat", + Usage: "Print eth/filecoin addrs and code cid", + ArgsUsage: "address", Action: func(cctx *cli.Context) error { - - filAddr := cctx.String("filAddr") - ethAddr := cctx.String("ethAddr") - - var faddr address.Address - var eaddr ethtypes.EthAddress + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } api, closer, err := GetFullNodeAPI(cctx) if err != nil { @@ -65,26 +53,25 @@ var EvmGetInfoCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) - if filAddr != "" { - addr, err := address.NewFromString(filAddr) - if err != nil { - return err - } - eaddr, faddr, err = ethAddrFromFilecoinAddress(ctx, addr, api) - if err != nil { - return err - } - } else if ethAddr != "" { - eaddr, err = ethtypes.ParseEthAddress(ethAddr) - if err != nil { - return err + addrString := cctx.Args().Get(0) + + var faddr address.Address + var eaddr ethtypes.EthAddress + addr, err := address.NewFromString(addrString) + if err != nil { // This isn't a filecoin address + eaddr, err = ethtypes.ParseEthAddress(addrString) + if err != nil { // This isn't an Eth address either + return xerrors.Errorf("address is not a filecoin or eth address") } faddr, err = eaddr.ToFilecoinAddress() if err != nil { return err } } else { - return xerrors.Errorf("Neither filAddr nor ethAddr specified") + eaddr, faddr, err = ethAddrFromFilecoinAddress(ctx, addr, api) + if err != nil { + return err + } } actor, err := api.StateGetActor(ctx, faddr, types.EmptyTSK) @@ -97,7 +84,6 @@ var EvmGetInfoCmd = &cli.Command{ fmt.Println("Code cid: ", actor.Code.String()) return nil - }, } @@ -121,7 +107,7 @@ var EvmCallSimulateCmd = &cli.Command{ return err } - params, err := hex.DecodeString(cctx.Args().Get(2)) + params, err := ethtypes.DecodeHexString(cctx.Args().Get(2)) if err != nil { return err } @@ -165,7 +151,7 @@ var EvmGetContractAddress = &cli.Command{ return err } - salt, err := hex.DecodeString(cctx.Args().Get(1)) + salt, err := ethtypes.DecodeHexString(cctx.Args().Get(1)) if err != nil { return xerrors.Errorf("Could not decode salt: %w", err) } @@ -184,7 +170,7 @@ var EvmGetContractAddress = &cli.Command{ return err } - contract, err := hex.DecodeString(string(contractHex)) + contract, err := ethtypes.DecodeHexString(string(contractHex)) if err != nil { return xerrors.Errorf("Could not decode contract file: %w", err) } @@ -233,7 +219,7 @@ var EvmDeployCmd = &cli.Command{ return xerrors.Errorf("failed to read contract: %w", err) } if cctx.Bool("hex") { - contract, err = hex.DecodeString(string(contract)) + contract, err = ethtypes.DecodeHexString(string(contract)) if err != nil { return xerrors.Errorf("failed to decode contract: %w", err) } @@ -345,8 +331,8 @@ var EvmInvokeCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) - if argc := cctx.Args().Len(); argc < 2 || argc > 3 { - return xerrors.Errorf("must pass the address, entry point and (optionally) input data") + if argc := cctx.Args().Len(); argc != 2 { + return xerrors.Errorf("must pass the address and calldata") } addr, err := address.NewFromString(cctx.Args().Get(0)) @@ -355,7 +341,7 @@ var EvmInvokeCmd = &cli.Command{ } var calldata []byte - calldata, err = hex.DecodeString(cctx.Args().Get(2)) + calldata, err = ethtypes.DecodeHexString(cctx.Args().Get(1)) if err != nil { return xerrors.Errorf("decoding hex input data: %w", err) } @@ -388,7 +374,7 @@ var EvmInvokeCmd = &cli.Command{ To: addr, From: fromAddr, Value: val, - Method: abi.MethodNum(2), + Method: builtintypes.MethodsEVM.InvokeContract, Params: calldata, } diff --git a/cli/send.go b/cli/send.go index 4268f8eb2..3e390584d 100644 --- a/cli/send.go +++ b/cli/send.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" ) var sendCmd = &cli.Command{ @@ -24,6 +25,10 @@ var sendCmd = &cli.Command{ Name: "from", Usage: "optionally specify the account to send funds from", }, + &cli.StringFlag{ + Name: "from-eth-addr", + Usage: "optionally specify the eth addr to send funds from", + }, &cli.StringFlag{ Name: "gas-premium", Usage: "specify gas price to use in AttoFIL", @@ -98,6 +103,18 @@ var sendCmd = &cli.Command{ } params.From = addr + } else if from := cctx.String("from-eth-addr"); from != "" { + eaddr, err := ethtypes.ParseEthAddress(from) + if err != nil { + return err + } + faddr, err := eaddr.ToFilecoinAddress() + if err != nil { + fmt.Println("error on conversion to faddr") + return err + } + fmt.Println("f4 addr: ", faddr) + params.From = faddr } if cctx.IsSet("gas-premium") { diff --git a/cli/state.go b/cli/state.go index 28e2a7e26..d4f5da129 100644 --- a/cli/state.go +++ b/cli/state.go @@ -776,7 +776,9 @@ var StateGetActorCmd = &cli.Command{ fmt.Printf("Nonce:\t\t%d\n", a.Nonce) fmt.Printf("Code:\t\t%s (%s)\n", a.Code, strtype) fmt.Printf("Head:\t\t%s\n", a.Head) - fmt.Printf("Delegated address:\t\t%s\n", a.Address) + if a.Address != nil { + fmt.Printf("Delegated address:\t\t%s\n", a.Address) + } return nil }, diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index fe5dd542c..2c853754b 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -83,11 +83,13 @@ * [EthGetFilterChanges](#EthGetFilterChanges) * [EthGetFilterLogs](#EthGetFilterLogs) * [EthGetLogs](#EthGetLogs) + * [EthGetMessageCidByTransactionHash](#EthGetMessageCidByTransactionHash) * [EthGetStorageAt](#EthGetStorageAt) * [EthGetTransactionByBlockHashAndIndex](#EthGetTransactionByBlockHashAndIndex) * [EthGetTransactionByBlockNumberAndIndex](#EthGetTransactionByBlockNumberAndIndex) * [EthGetTransactionByHash](#EthGetTransactionByHash) * [EthGetTransactionCount](#EthGetTransactionCount) + * [EthGetTransactionHashByCid](#EthGetTransactionHashByCid) * [EthGetTransactionReceipt](#EthGetTransactionReceipt) * [EthMaxPriorityFeePerGas](#EthMaxPriorityFeePerGas) * [EthNewBlockFilter](#EthNewBlockFilter) @@ -2640,6 +2642,25 @@ Response: ] ``` +### EthGetMessageCidByTransactionHash + + +Perms: read + +Inputs: +```json +[ + "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e" +] +``` + +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` + ### EthGetStorageAt @@ -2778,6 +2799,22 @@ Inputs: Response: `"0x5"` +### EthGetTransactionHashByCid + + +Perms: read + +Inputs: +```json +[ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } +] +``` + +Response: `"0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e"` + ### EthGetTransactionReceipt diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index d5be0009b..fbb020b0e 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -182,15 +182,16 @@ CATEGORY: BASIC OPTIONS: - --force Deprecated: use global 'force-send' (default: false) - --from value optionally specify the account to send funds from - --gas-feecap value specify gas fee cap to use in AttoFIL (default: "0") - --gas-limit value specify gas limit (default: 0) - --gas-premium value specify gas price to use in AttoFIL (default: "0") - --method value specify method to invoke (default: 0) - --nonce value specify the nonce to use (default: 0) - --params-hex value specify invocation parameters in hex - --params-json value specify invocation parameters in json + --force Deprecated: use global 'force-send' (default: false) + --from value optionally specify the account to send funds from + --from-eth-addr value optionally specify the eth addr to send funds from + --gas-feecap value specify gas fee cap to use in AttoFIL (default: "0") + --gas-limit value specify gas limit (default: 0) + --gas-premium value specify gas price to use in AttoFIL (default: "0") + --method value specify method to invoke (default: 0) + --nonce value specify the nonce to use (default: 0) + --params-hex value specify invocation parameters in hex + --params-json value specify invocation parameters in json ``` @@ -2624,11 +2625,10 @@ NAME: lotus evm stat - Print eth/filecoin addrs and code cid USAGE: - lotus evm stat [command options] [arguments...] + lotus evm stat [command options] address OPTIONS: - --ethAddr value Ethereum address - --filAddr value Filecoin address + --help, -h show help (default: false) ``` diff --git a/documentation/en/default-lotus-config.toml b/documentation/en/default-lotus-config.toml index 7a1ab7bc5..41d7e6aca 100644 --- a/documentation/en/default-lotus-config.toml +++ b/documentation/en/default-lotus-config.toml @@ -293,53 +293,71 @@ #Tracing = false -[ActorEvent] - # EnableRealTimeFilterAPI enables APIs that can create and query filters for actor events as they are emitted. +[Fevm] + # EnableEthRPC enables eth_ rpc, and enables storing a mapping of eth transaction hashes to filecoin message Cids. + # This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above. # # type: bool - # env var: LOTUS_ACTOREVENT_ENABLEREALTIMEFILTERAPI - #EnableRealTimeFilterAPI = false + # env var: LOTUS_FEVM_ENABLEETHRPC + #EnableEthRPC = false - # EnableHistoricFilterAPI enables APIs that can create and query filters for actor events that occurred in the past. - # A queryable index of events will be maintained. - # - # type: bool - # env var: LOTUS_ACTOREVENT_ENABLEHISTORICFILTERAPI - #EnableHistoricFilterAPI = false - - # FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than - # this time become eligible for automatic deletion. - # - # type: Duration - # env var: LOTUS_ACTOREVENT_FILTERTTL - #FilterTTL = "24h0m0s" - - # MaxFilters specifies the maximum number of filters that may exist at any one time. + # EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days + # Set to 0 to keep all mappings # # type: int - # env var: LOTUS_ACTOREVENT_MAXFILTERS - #MaxFilters = 100 + # env var: LOTUS_FEVM_ETHTXHASHMAPPINGLIFETIMEDAYS + #EthTxHashMappingLifetimeDays = 0 - # MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter. - # - # type: int - # env var: LOTUS_ACTOREVENT_MAXFILTERRESULTS - #MaxFilterResults = 10000 + [Fevm.Events] + # EnableEthRPC enables APIs that + # DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. + # The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + # + # type: bool + # env var: LOTUS_FEVM_EVENTS_DISABLEREALTIMEFILTERAPI + #DisableRealTimeFilterAPI = false - # MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying - # the entire chain) - # - # type: uint64 - # env var: LOTUS_ACTOREVENT_MAXFILTERHEIGHTRANGE - #MaxFilterHeightRange = 2880 + # DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events + # that occurred in the past. HistoricFilterAPI maintains a queryable index of events. + # The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + # + # type: bool + # env var: LOTUS_FEVM_EVENTS_DISABLEHISTORICFILTERAPI + #DisableHistoricFilterAPI = false - # ActorEventDatabasePath is the full path to a sqlite database that will be used to index actor events to - # support the historic filter APIs. If the database does not exist it will be created. The directory containing - # the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as - # relative to the CWD (current working directory). - # - # type: string - # env var: LOTUS_ACTOREVENT_ACTOREVENTDATABASEPATH - #ActorEventDatabasePath = "" + # FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than + # this time become eligible for automatic deletion. + # + # type: Duration + # env var: LOTUS_FEVM_EVENTS_FILTERTTL + #FilterTTL = "24h0m0s" + + # MaxFilters specifies the maximum number of filters that may exist at any one time. + # + # type: int + # env var: LOTUS_FEVM_EVENTS_MAXFILTERS + #MaxFilters = 100 + + # MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter. + # + # type: int + # env var: LOTUS_FEVM_EVENTS_MAXFILTERRESULTS + #MaxFilterResults = 10000 + + # MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying + # the entire chain) + # + # type: uint64 + # env var: LOTUS_FEVM_EVENTS_MAXFILTERHEIGHTRANGE + #MaxFilterHeightRange = 2880 + + # DatabasePath is the full path to a sqlite database that will be used to index actor events to + # support the historic filter APIs. If the database does not exist it will be created. The directory containing + # the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as + # relative to the CWD (current working directory). + # + # type: string + # env var: LOTUS_FEVM_EVENTS_DATABASEPATH + #DatabasePath = "" diff --git a/itests/contracts/DelegatecallActor.hex b/itests/contracts/DelegatecallActor.hex new file mode 100644 index 000000000..aed647407 --- /dev/null +++ b/itests/contracts/DelegatecallActor.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5061018c806100206000396000f3fe6080604052600436106100345760003560e01c806361bc221a146100395780636466414b146100645780638ada066e14610080575b600080fd5b34801561004557600080fd5b5061004e6100ab565b60405161005b91906100dd565b60405180910390f35b61007e60048036038101906100799190610129565b6100b1565b005b34801561008c57600080fd5b506100956100bb565b6040516100a291906100dd565b60405180910390f35b60005481565b8060008190555050565b60008054905090565b6000819050919050565b6100d7816100c4565b82525050565b60006020820190506100f260008301846100ce565b92915050565b600080fd5b610106816100c4565b811461011157600080fd5b50565b600081359050610123816100fd565b92915050565b60006020828403121561013f5761013e6100f8565b5b600061014d84828501610114565b9150509291505056fea2646970667358221220cf4567855a30be48cde5cdbff495bdaa4052e2c4540678b97284af53a4e5dbd164736f6c63430008110033 \ No newline at end of file diff --git a/itests/contracts/DelegatecallActor.sol b/itests/contracts/DelegatecallActor.sol new file mode 100644 index 000000000..8671f6298 --- /dev/null +++ b/itests/contracts/DelegatecallActor.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract DelegatecallActor { + uint public counter; + + function getCounter() public view returns (uint){ + return counter; + } + function setVars(uint _counter) public payable { + counter = _counter; + } +} diff --git a/itests/contracts/DelegatecallStorage.hex b/itests/contracts/DelegatecallStorage.hex new file mode 100644 index 000000000..7ce9ef210 --- /dev/null +++ b/itests/contracts/DelegatecallStorage.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50610477806100206000396000f3fe6080604052600436106100345760003560e01c806361bc221a146100395780638ada066e14610064578063d1e0f3081461008f575b600080fd5b34801561004557600080fd5b5061004e6100bf565b60405161005b919061022c565b60405180910390f35b34801561007057600080fd5b506100796100c5565b604051610086919061022c565b60405180910390f35b6100a960048036038101906100a491906102d6565b6100ce565b6040516100b6919061022c565b60405180910390f35b60005481565b60008054905090565b6000808373ffffffffffffffffffffffffffffffffffffffff16836040516024016100f9919061022c565b6040516020818303038152906040527f6466414b000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516101839190610387565b600060405180830381855af49150503d80600081146101be576040519150601f19603f3d011682016040523d82523d6000602084013e6101c3565b606091505b5050905080610207576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101fe90610421565b60405180910390fd5b60005491505092915050565b6000819050919050565b61022681610213565b82525050565b6000602082019050610241600083018461021d565b92915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102778261024c565b9050919050565b6102878161026c565b811461029257600080fd5b50565b6000813590506102a48161027e565b92915050565b6102b381610213565b81146102be57600080fd5b50565b6000813590506102d0816102aa565b92915050565b600080604083850312156102ed576102ec610247565b5b60006102fb85828601610295565b925050602061030c858286016102c1565b9150509250929050565b600081519050919050565b600081905092915050565b60005b8381101561034a57808201518184015260208101905061032f565b60008484015250505050565b600061036182610316565b61036b8185610321565b935061037b81856020860161032c565b80840191505092915050565b60006103938284610356565b915081905092915050565b600082825260208201905092915050565b7f4572726f72206d6573736167653a2044656c656761746563616c6c206661696c60008201527f6564000000000000000000000000000000000000000000000000000000000000602082015250565b600061040b60228361039e565b9150610416826103af565b604082019050919050565b6000602082019050818103600083015261043a816103fe565b905091905056fea26469706673582212203663909b8221e9b87047be99420c00339af1430c085260df209b909ed8e0f05164736f6c63430008110033 \ No newline at end of file diff --git a/itests/contracts/DelegatecallStorage.sol b/itests/contracts/DelegatecallStorage.sol new file mode 100644 index 000000000..434cd934e --- /dev/null +++ b/itests/contracts/DelegatecallStorage.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract DelegatecallStorage { + uint public counter; + + function getCounter() public view returns (uint){ + return counter; + } + function setVars(address _contract, uint _counter) public payable returns (uint){ + (bool success, ) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _counter) + ); + require(success, 'Error message: Delegatecall failed'); + return counter; + } +} diff --git a/itests/contracts/EventMatrix.hex b/itests/contracts/EventMatrix.hex new file mode 100644 index 000000000..2b3ad91ad --- /dev/null +++ b/itests/contracts/EventMatrix.hex @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506105eb806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a95760003560e01c8063c755553811610071578063c755553814610198578063cbfc3b58146101c6578063cc6f8faf14610212578063cd5b6c3d14610254578063e2a614731461028c578063fb62b28b146102d8576100a9565b80630919b8be146100ae5780636199074d146100e657806366eef3461461012857806375091b1f14610132578063a63ae81a1461016a575b600080fd5b6100e4600480360360408110156100c457600080fd5b81019080803590602001909291908035906020019092919050505061031a565b005b610126600480360360608110156100fc57600080fd5b8101908080359060200190929190803590602001909291908035906020019092919050505061035d565b005b610130610391565b005b6101686004803603604081101561014857600080fd5b8101908080359060200190929190803590602001909291905050506103bf565b005b6101966004803603602081101561018057600080fd5b81019080803590602001909291905050506103fb565b005b6101c4600480360360208110156101ae57600080fd5b8101908080359060200190929190505050610435565b005b610210600480360360808110156101dc57600080fd5b8101908080359060200190929190803590602001909291908035906020019092919080359060200190929190505050610465565b005b6102526004803603606081101561022857600080fd5b810190808035906020019092919080359060200190929190803590602001909291905050506104ba565b005b61028a6004803603604081101561026a57600080fd5b8101908080359060200190929190803590602001909291905050506104f8565b005b6102d6600480360360808110156102a257600080fd5b810190808035906020019092919080359060200190929190803590602001909291908035906020019092919050505061052a565b005b610318600480360360608110156102ee57600080fd5b8101908080359060200190929190803590602001909291908035906020019092919050505061056a565b005b7f5469c6b769315f5668523937f05ca07d4cc87849432bc5f5907f1d90fa73b9f98282604051808381526020018281526020019250505060405180910390a15050565b8082847fb89dabcdb7ff41f1794c0da92f65ece6c19b6b0caeac5407b2a721efe27c080460405160405180910390a4505050565b7fc3f6f1c76bd4e74ee5782052b0b4f8bd5c50b86c3c5a2f52638e03066e50a91b60405160405180910390a1565b817f6709824ebe5f6e620ca3f4b02a3428e8ce2dc97c550816eaeeb3a342b214bd85826040518082815260200191505060405180910390a25050565b7fc804e53d6048af1b3e6a352e246d5f3864fea9d635ace499e023a58c383b3a88816040518082815260200191505060405180910390a150565b807f44a227a31429ab5eb00daf6611c6422f10571619f2267e0e149e9ebe6d2a5d0560405160405180910390a250565b7f28d45631a87b2a52a9625f8520fa37ff8c4d926cdf17042e241985da5cb7b850848484846040518085815260200184815260200183815260200182815260200194505050505060405180910390a150505050565b81837fcd5fe5fbc1d27b90036997224cea7aa565e3779622867265081f636b3a5ccb08836040518082815260200191505060405180910390a3505050565b80827f232f09cef3babc26e58d1cc1346c0a8bc626ffe600c9605b5d747783eda484a760405160405180910390a35050565b8183857f812e73dbcf7e267f27ecb1383bfc902a6650b41b6e7d03ac265108c369673d95846040518082815260200191505060405180910390a450505050565b7fd4d143faaf60340ad98e1f2c96fc26f5695834c21b5200edad339ee7e9a372cc83838360405180848152602001838152602001828152602001935050505060405180910390a150505056fea265627a7a72315820954561fde80ab925299e0a9f3356b01f64fb1976dd335ac2ebd9367441e29f0564736f6c63430005110032 diff --git a/itests/contracts/EventMatrix.sol b/itests/contracts/EventMatrix.sol new file mode 100644 index 000000000..bd008e27b --- /dev/null +++ b/itests/contracts/EventMatrix.sol @@ -0,0 +1,51 @@ +pragma solidity ^0.5.0; + +contract EventMatrix { + event EventZeroData(); + event EventOneData(uint a); + event EventTwoData(uint a, uint b); + event EventThreeData(uint a, uint b, uint c); + event EventFourData(uint a, uint b, uint c, uint d); + + event EventOneIndexed(uint indexed a); + event EventTwoIndexed(uint indexed a, uint indexed b); + event EventThreeIndexed(uint indexed a, uint indexed b, uint indexed c); + + event EventOneIndexedWithData(uint indexed a, uint b); + event EventTwoIndexedWithData(uint indexed a, uint indexed b, uint c); + event EventThreeIndexedWithData(uint indexed a, uint indexed b, uint indexed c, uint d); + + function logEventZeroData() public { + emit EventZeroData(); + } + function logEventOneData(uint a) public { + emit EventOneData(a); + } + function logEventTwoData(uint a, uint b) public { + emit EventTwoData(a,b); + } + function logEventThreeData(uint a, uint b, uint c) public { + emit EventThreeData(a,b,c); + } + function logEventFourData(uint a, uint b, uint c, uint d) public { + emit EventFourData(a,b,c,d); + } + function logEventOneIndexed(uint a) public { + emit EventOneIndexed(a); + } + function logEventTwoIndexed(uint a, uint b) public { + emit EventTwoIndexed(a,b); + } + function logEventThreeIndexed(uint a, uint b, uint c) public { + emit EventThreeIndexed(a,b,c); + } + function logEventOneIndexedWithData(uint a, uint b) public { + emit EventOneIndexedWithData(a,b); + } + function logEventTwoIndexedWithData(uint a, uint b, uint c) public { + emit EventTwoIndexedWithData(a,b,c); + } + function logEventThreeIndexedWithData(uint a, uint b, uint c, uint d) public { + emit EventThreeIndexedWithData(a,b,c,d); + } +} diff --git a/itests/contracts/SimpleCoin.hex b/itests/contracts/SimpleCoin.hex index 55b83cb12..8e11ab5b9 100644 --- a/itests/contracts/SimpleCoin.hex +++ b/itests/contracts/SimpleCoin.hex @@ -1 +1 @@ -608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061051c806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061047e565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104b2565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b925082820261046081610337565b915082820484148315176104775761047661040d565b5b5092915050565b600061048982610337565b915061049483610337565b92508282039050818111156104ac576104ab61040d565b5b92915050565b60006104bd82610337565b91506104c883610337565b92508282019050808211156104e0576104df61040d565b5b9291505056fea26469706673582212205ede41ff9072784ccc19ac18de0781558d305a8139361fa85dc51a8614e47d8c64736f6c63430008110033 \ No newline at end of file +608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061051c806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061047e565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104b2565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b925082820261046081610337565b915082820484148315176104775761047661040d565b5b5092915050565b600061048982610337565b915061049483610337565b92508282039050818111156104ac576104ab61040d565b5b92915050565b60006104bd82610337565b91506104c883610337565b92508282019050808211156104e0576104df61040d565b5b9291505056fea2646970667358221220050cdcfbe2911d041d2e6c355dbb6a0ca8ca70b500865bf33d9a2e5f4ac5a4e164736f6c63430008110033 \ No newline at end of file diff --git a/itests/contracts/compile.sh b/itests/contracts/compile.sh new file mode 100755 index 000000000..1163ad9a6 --- /dev/null +++ b/itests/contracts/compile.sh @@ -0,0 +1,6 @@ +#use the solc compiler https://docs.soliditylang.org/en/v0.8.17/installing-solidity.html +# to compile all of the .sol files to their corresponding evm binary files stored as .hex +# solc outputs to stdout a format that we just want to grab the last line of and then remove the trailing newline on that line + +find -type f -name \*.sol -print0 | + xargs -0 -I{} bash -c 'solc --bin {} |tail -n1 | tr -d "\n" > $(echo {} | sed -e s/.sol$/.hex/)' diff --git a/itests/contracts/events.asm b/itests/contracts/events.asm new file mode 100644 index 000000000..ab96fcedd --- /dev/null +++ b/itests/contracts/events.asm @@ -0,0 +1,47 @@ +# https://github.com/filecoin-project/builtin-actors/blob/b1ba61053de2ceaddd5116e87823d20a8f5e38d7/actors/evm/tests/events.rs +# method dispatch: +# - 0x00000000 -> log_zero_data +# - 0x00000001 -> log_zero_nodata +# - 0x00000002 -> log_four_data +%dispatch_begin() +%dispatch(0x00, log_zero_data) +%dispatch(0x01, log_zero_nodata) +%dispatch(0x02, log_four_data) +%dispatch_end() +#### log a zero topic event with data +log_zero_data: +jumpdest +push8 0x1122334455667788 +push1 0x00 +mstore +push1 0x08 +push1 0x18 ## index 24 into memory as mstore writes a full word +log0 +push1 0x00 +push1 0x00 +return +#### log a zero topic event with no data +log_zero_nodata: +jumpdest +push1 0x00 +push1 0x00 +log0 +push1 0x00 +push1 0x00 +return +#### log a four topic event with data +log_four_data: +jumpdest +push8 0x1122334455667788 +push1 0x00 +mstore +push4 0x4444 +push3 0x3333 +push2 0x2222 +push2 0x1111 +push1 0x08 +push1 0x18 ## index 24 into memory as mstore writes a full word +log4 +push1 0x00 +push1 0x00 +return diff --git a/itests/deals_padding_test.go b/itests/deals_padding_test.go index 3535a1227..aaca45360 100644 --- a/itests/deals_padding_test.go +++ b/itests/deals_padding_test.go @@ -33,7 +33,7 @@ func TestDealPadding(t *testing.T) { dh := kit.NewDealHarness(t, client, miner, miner) ctx := context.Background() - client.WaitTillChain(ctx, kit.BlockMinedBy(miner.ActorAddr)) + client.WaitTillChain(ctx, kit.BlocksMinedByAll(miner.ActorAddr)) // Create a random file, would originally be a 256-byte sector res, inFile := client.CreateImportFile(ctx, 1, 200) diff --git a/itests/deals_power_test.go b/itests/deals_power_test.go index 1ca28c6fd..57483cde7 100644 --- a/itests/deals_power_test.go +++ b/itests/deals_power_test.go @@ -52,7 +52,7 @@ func TestFirstDealEnablesMining(t *testing.T) { providerMined := make(chan struct{}) go func() { - _ = client.WaitTillChain(ctx, kit.BlockMinedBy(provider.ActorAddr)) + _ = client.WaitTillChain(ctx, kit.BlocksMinedByAll(provider.ActorAddr)) close(providerMined) }() diff --git a/itests/eth_block_hash_test.go b/itests/eth_block_hash_test.go new file mode 100644 index 000000000..ac6506bb2 --- /dev/null +++ b/itests/eth_block_hash_test.go @@ -0,0 +1,65 @@ +package itests + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/itests/kit" +) + +// TestEthBlockHashesCorrect_MultiBlockTipset validates that blocks retrieved through +// EthGetBlockByNumber are identical to blocks retrieved through +// EthGetBlockByHash, when using the block hash returned by the former. +// +// Specifically, it checks the system behaves correctly with multiblock tipsets. +// +// Catches regressions around https://github.com/filecoin-project/lotus/issues/10061. +func TestEthBlockHashesCorrect_MultiBlockTipset(t *testing.T) { + // miner is connected to the first node, and we want to observe the chain + // from the second node. + blocktime := 100 * time.Millisecond + n1, m1, m2, ens := kit.EnsembleOneTwo(t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + n1.WaitTillChain(ctx, kit.HeightAtLeast(abi.ChainEpoch(25))) + defer cancel() + + var n2 kit.TestFullNode + ens.FullNode(&n2, kit.ThroughRPC()).Start().Connect(n2, n1) + + // find the first tipset where all miners mined a block. + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Minute) + n2.WaitTillChain(ctx, kit.BlocksMinedByAll(m1.ActorAddr, m2.ActorAddr)) + defer cancel() + + head, err := n2.ChainHead(context.Background()) + require.NoError(t, err) + + // let the chain run a little bit longer to minimise the chance of reorgs + n2.WaitTillChain(ctx, kit.HeightAtLeast(head.Height()+50)) + + head, err = n2.ChainHead(context.Background()) + require.NoError(t, err) + + for i := 1; i <= int(head.Height()); i++ { + hex := fmt.Sprintf("0x%x", i) + + ethBlockA, err := n2.EthGetBlockByNumber(ctx, hex, true) + require.NoError(t, err) + + ethBlockB, err := n2.EthGetBlockByHash(ctx, ethBlockA.Hash, true) + require.NoError(t, err) + + require.Equal(t, ethBlockA, ethBlockB) + } +} diff --git a/itests/eth_deploy_test.go b/itests/eth_deploy_test.go index 13a68ce46..98038de7b 100644 --- a/itests/eth_deploy_test.go +++ b/itests/eth_deploy_test.go @@ -20,7 +20,6 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/itests/kit" - "github.com/filecoin-project/lotus/node/config" ) // TestDeployment smoke tests the deployment of a contract via the @@ -36,12 +35,7 @@ func TestDeployment(t *testing.T) { client, _, ens := kit.EnsembleMinimal( t, kit.MockProofs(), - kit.ThroughRPC(), - kit.WithCfgOpt(func(cfg *config.FullNode) error { - cfg.ActorEvent.EnableRealTimeFilterAPI = true - return nil - }), - ) + kit.ThroughRPC()) ens.InterconnectAll().BeginMining(blockTime) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) @@ -99,6 +93,7 @@ func TestDeployment(t *testing.T) { mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) require.NoError(t, err) + require.NotNil(t, mpoolTx) // require that the hashes are identical require.Equal(t, hash, mpoolTx.Hash) diff --git a/itests/eth_filter_test.go b/itests/eth_filter_test.go index bee9a8c38..aba61f934 100644 --- a/itests/eth_filter_test.go +++ b/itests/eth_filter_test.go @@ -2,20 +2,27 @@ package itests import ( + "bytes" "context" + "encoding/binary" "encoding/hex" "encoding/json" + "fmt" "os" - "path/filepath" + "sort" "strconv" "strings" "testing" "time" + "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/crypto/sha3" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/api" @@ -25,12 +32,60 @@ import ( "github.com/filecoin-project/lotus/itests/kit" ) +// SolidityContractDef holds information about one of the test contracts +type SolidityContractDef struct { + Filename string // filename of the hex of the contract, e.g. contracts/EventMatrix.hex + Fn map[string][]byte // mapping of function names to 32-bit selector + Ev map[string][]byte // mapping of event names to 256-bit signature hashes +} + +var EventMatrixContract = SolidityContractDef{ + Filename: "contracts/EventMatrix.hex", + Fn: map[string][]byte{ + "logEventZeroData": ethFunctionHash("logEventZeroData()"), + "logEventOneData": ethFunctionHash("logEventOneData(uint256)"), + "logEventTwoData": ethFunctionHash("logEventTwoData(uint256,uint256)"), + "logEventThreeData": ethFunctionHash("logEventThreeData(uint256,uint256,uint256)"), + "logEventFourData": ethFunctionHash("logEventFourData(uint256,uint256,uint256,uint256)"), + "logEventOneIndexed": ethFunctionHash("logEventOneIndexed(uint256)"), + "logEventTwoIndexed": ethFunctionHash("logEventTwoIndexed(uint256,uint256)"), + "logEventThreeIndexed": ethFunctionHash("logEventThreeIndexed(uint256,uint256,uint256)"), + "logEventOneIndexedWithData": ethFunctionHash("logEventOneIndexedWithData(uint256,uint256)"), + "logEventTwoIndexedWithData": ethFunctionHash("logEventTwoIndexedWithData(uint256,uint256,uint256)"), + "logEventThreeIndexedWithData": ethFunctionHash("logEventThreeIndexedWithData(uint256,uint256,uint256,uint256)"), + }, + Ev: map[string][]byte{ + "EventZeroData": ethTopicHash("EventZeroData()"), + "EventOneData": ethTopicHash("EventOneData(uint256)"), + "EventTwoData": ethTopicHash("EventTwoData(uint256,uint256)"), + "EventThreeData": ethTopicHash("EventThreeData(uint256,uint256,uint256)"), + "EventFourData": ethTopicHash("EventFourData(uint256,uint256,uint256,uint256)"), + "EventOneIndexed": ethTopicHash("EventOneIndexed(uint256)"), + "EventTwoIndexed": ethTopicHash("EventTwoIndexed(uint256,uint256)"), + "EventThreeIndexed": ethTopicHash("EventThreeIndexed(uint256,uint256,uint256)"), + "EventOneIndexedWithData": ethTopicHash("EventOneIndexedWithData(uint256,uint256)"), + "EventTwoIndexedWithData": ethTopicHash("EventTwoIndexedWithData(uint256,uint256,uint256)"), + "EventThreeIndexedWithData": ethTopicHash("EventThreeIndexedWithData(uint256,uint256,uint256,uint256)"), + }, +} + +var EventsContract = SolidityContractDef{ + Filename: "contracts/events.bin", + Fn: map[string][]byte{ + "log_zero_data": {0x00, 0x00, 0x00, 0x00}, + "log_zero_nodata": {0x00, 0x00, 0x00, 0x01}, + "log_four_data": {0x00, 0x00, 0x00, 0x02}, + }, + Ev: map[string][]byte{}, +} + func TestEthNewPendingTransactionFilter(t *testing.T) { - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() - kit.QuietMiningLogs() + kit.QuietAllLogsExcept("events", "messagepool") - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI()) + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.WithEthRPC()) ens.InterconnectAll().BeginMining(10 * time.Millisecond) // create a new address where to send funds. @@ -58,9 +113,15 @@ func TestEthNewPendingTransactionFilter(t *testing.T) { require.NoError(t, err) <-headChangeCh // skip hccurrent + defer func() { + close(waitAllCh) + }() + count := 0 for { select { + case <-ctx.Done(): + return case headChanges := <-headChangeCh: for _, change := range headChanges { if change.Type == store.HCApply { @@ -68,7 +129,7 @@ func TestEthNewPendingTransactionFilter(t *testing.T) { require.NoError(t, err) count += len(msgs) if count == iterations { - waitAllCh <- struct{}{} + return } } } @@ -93,8 +154,8 @@ func TestEthNewPendingTransactionFilter(t *testing.T) { select { case <-waitAllCh: - case <-time.After(time.Minute): - t.Errorf("timeout to wait for pack messages") + case <-ctx.Done(): + t.Errorf("timeout waiting to pack messages") } expected := make(map[string]bool) @@ -109,7 +170,10 @@ func TestEthNewPendingTransactionFilter(t *testing.T) { require.NoError(t, err) // expect to have seen iteration number of mpool messages - require.Equal(t, iterations, len(res.Results)) + require.Equal(t, iterations, len(res.Results), "expected %d tipsets to have been executed", iterations) + + require.Equal(t, len(res.Results), len(expected), "expected number of filter results to equal number of messages") + for _, txid := range res.Results { expected[txid.(string)] = true } @@ -120,11 +184,12 @@ func TestEthNewPendingTransactionFilter(t *testing.T) { } func TestEthNewBlockFilter(t *testing.T) { - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() - kit.QuietMiningLogs() + kit.QuietAllLogsExcept("events", "messagepool") - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI()) + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.WithEthRPC()) ens.InterconnectAll().BeginMining(10 * time.Millisecond) // create a new address where to send funds. @@ -147,20 +212,29 @@ func TestEthNewBlockFilter(t *testing.T) { each := big.Div(toSend, big.NewInt(iterations)) waitAllCh := make(chan struct{}) + tipsetChan := make(chan *types.TipSet, iterations) go func() { headChangeCh, err := client.ChainNotify(ctx) require.NoError(t, err) <-headChangeCh // skip hccurrent + defer func() { + close(tipsetChan) + close(waitAllCh) + }() + count := 0 for { select { + case <-ctx.Done(): + return case headChanges := <-headChangeCh: for _, change := range headChanges { if change.Type == store.HCApply || change.Type == store.HCRevert { count++ + tipsetChan <- change.Val if count == iterations { - waitAllCh <- struct{}{} + return } } } @@ -168,7 +242,6 @@ func TestEthNewBlockFilter(t *testing.T) { } }() - // var sms []*types.SignedMessage for i := 0; i < iterations; i++ { msg := &types.Message{ From: client.DefaultKey.Address, @@ -179,15 +252,21 @@ func TestEthNewBlockFilter(t *testing.T) { sm, err := client.MpoolPushMessage(ctx, msg, nil) require.NoError(t, err) require.EqualValues(t, i, sm.Message.Nonce) - - // FIXME this was here and unused. Use or remove. - // sms = append(sms, sm) } select { case <-waitAllCh: - case <-time.After(time.Minute): - t.Errorf("timeout to wait for pack messages") + case <-ctx.Done(): + t.Errorf("timeout waiting to pack messages") + } + + expected := make(map[string]bool) + for ts := range tipsetChan { + c, err := ts.Key().Cid() + require.NoError(t, err) + hash, err := ethtypes.EthHashFromCid(c) + require.NoError(t, err) + expected[hash.String()] = false } // collect filter results @@ -195,16 +274,26 @@ func TestEthNewBlockFilter(t *testing.T) { require.NoError(t, err) // expect to have seen iteration number of tipsets - require.Equal(t, iterations, len(res.Results)) + require.Equal(t, iterations, len(res.Results), "expected %d tipsets to have been executed", iterations) + + require.Equal(t, len(res.Results), len(expected), "expected number of filter results to equal number of tipsets") + + for _, blockhash := range res.Results { + expected[blockhash.(string)] = true + } + + for _, found := range expected { + require.True(t, found, "expected all tipsets to be present in filter results") + } } func TestEthNewFilterCatchAll(t *testing.T) { require := require.New(t) - kit.QuietMiningLogs() + kit.QuietAllLogsExcept("events", "messagepool") blockTime := 100 * time.Millisecond - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI()) + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.WithEthRPC()) ens.InterconnectAll().BeginMining(blockTime) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) @@ -230,24 +319,1033 @@ func TestEthNewFilterCatchAll(t *testing.T) { filterID, err := client.EthNewFilter(ctx, ðtypes.EthFilterSpec{}) require.NoError(err) - const iterations = 10 + const iterations = 3 + ethContractAddr, received := invokeLogFourData(t, client, iterations) - type msgInTipset struct { - msg api.Message - ts *types.TipSet + // collect filter results + res, err := client.EthGetFilterChanges(ctx, filterID) + require.NoError(err) + + // expect to have seen iteration number of events + require.Equal(iterations, len(res.Results)) + + expected := []ExpectedEthLog{ + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, } - msgChan := make(chan msgInTipset, iterations) + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, expected, received) +} + +func TestEthGetLogsAll(t *testing.T) { + require := require.New(t) + kit.QuietAllLogsExcept("events", "messagepool") + + 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() + + invocations := 1 + ethContractAddr, received := invokeLogFourData(t, client, invocations) + + // Build filter spec + spec := newEthFilterBuilder(). + FromBlockEpoch(0). + Topic1OneOf(paddedEthHash([]byte{0x11, 0x11})). + Filter() + + expected := []ExpectedEthLog{ + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + } + + // Use filter + res, err := client.EthGetLogs(ctx, spec) + require.NoError(err) + + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, expected, received) +} + +func TestEthGetLogsByTopic(t *testing.T) { + require := require.New(t) + + kit.QuietAllLogsExcept("events", "messagepool") + + blockTime := 100 * time.Millisecond + + client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + + invocations := 1 + ethContractAddr, received := invokeLogFourData(t, client, invocations) + + // find log by known topic1 + var spec ethtypes.EthFilterSpec + err := json.Unmarshal([]byte(`{"fromBlock":"0x0","topics":["0x0000000000000000000000000000000000000000000000000000000000001111"]}`), &spec) + require.NoError(err) + + res, err := client.EthGetLogs(context.Background(), &spec) + require.NoError(err) + + expected := []ExpectedEthLog{ + { + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + }, + } + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, expected, received) +} + +func TestEthSubscribeLogs(t *testing.T) { + require := require.New(t) + + kit.QuietAllLogsExcept("events", "messagepool") + + 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() + + // install contract + contractHex, err := os.ReadFile("contracts/events.bin") + require.NoError(err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(err) + + fromAddr, err := client.WalletDefaultAddress(ctx) + require.NoError(err) + + result := client.EVM().DeployContract(ctx, fromAddr, contract) + + idAddr, err := address.NewIDAddress(result.ActorID) + require.NoError(err) + t.Logf("actor ID address is %s", idAddr) + + // install filter + respCh, err := client.EthSubscribe(ctx, "logs", nil) + require.NoError(err) + + subResponses := []ethtypes.EthSubscriptionResponse{} + go func() { + for resp := range respCh { + subResponses = append(subResponses, resp) + } + }() + + const iterations = 10 + ethContractAddr, messages := invokeLogFourData(t, client, iterations) + + expected := make([]ExpectedEthLog, iterations) + for i := range expected { + expected[i] = ExpectedEthLog{ + Address: ethContractAddr, + Topics: []ethtypes.EthBytes{ + paddedEthBytes([]byte{0x11, 0x11}), + paddedEthBytes([]byte{0x22, 0x22}), + paddedEthBytes([]byte{0x33, 0x33}), + paddedEthBytes([]byte{0x44, 0x44}), + }, + Data: paddedEthBytes([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}), + } + } + + elogs, err := parseEthLogsFromSubscriptionResponses(subResponses) + require.NoError(err) + AssertEthLogs(t, elogs, expected, messages) +} + +func TestEthGetLogs(t *testing.T) { + require := require.New(t) + kit.QuietAllLogsExcept("events", "messagepool") + + 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() + + // Set up the test fixture with a standard list of invocations + contract1, contract2, messages := invokeEventMatrix(ctx, t, client) + + testCases := []struct { + name string + spec *ethtypes.EthFilterSpec + expected []ExpectedEthLog + }{ + { + name: "find all EventZeroData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventZeroData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventZeroData"], + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventZeroData"], + }, + Data: nil, + }, + }, + }, + { + name: "find all EventOneData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventOneData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneData"], + }, + Data: packUint64Values(23), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneData"], + }, + Data: packUint64Values(44), + }, + }, + }, + { + name: "find all EventTwoData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoData"], + }, + Data: packUint64Values(555, 666), + }, + }, + }, + { + name: "find all EventThreeData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventThreeData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeData"], + }, + Data: packUint64Values(1, 2, 3), + }, + }, + }, + { + name: "find all EventOneIndexed events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventOneIndexed"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexed"], + paddedUint64(44), + }, + Data: nil, + }, + }, + }, + { + name: "find all EventTwoIndexed events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoIndexed"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(44), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(40), + paddedUint64(20), + }, + Data: nil, + }, + }, + }, + { + name: "find all EventThreeIndexed events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventThreeIndexed"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexed"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: nil, + }, + }, + }, + { + name: "find all EventOneIndexedWithData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventOneIndexedWithData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(44), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(46), + }, + Data: paddedUint64(12), + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + { + name: "find all EventTwoIndexedWithData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(14), + }, + Data: paddedUint64(19), + }, + }, + }, + { + name: "find all EventThreeIndexedWithData events", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventThreeIndexedWithData"])).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: paddedUint64(12), + }, + }, + }, + + { + name: "find all events from contract2", + spec: newEthFilterBuilder().FromBlockEpoch(0).AddressOneOf(contract2).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventZeroData"], + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexed"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(44), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + + { + name: "find all events with topic2 of 44", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic2OneOf(paddedEthHash(paddedUint64(44))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexed"], + paddedUint64(44), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(44), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexed"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(44), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: paddedUint64(12), + }, + }, + }, + + { + name: "find all events with topic2 of 44 from contract2", + spec: newEthFilterBuilder().FromBlockEpoch(0).AddressOneOf(contract2).Topic2OneOf(paddedEthHash(paddedUint64(44))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventThreeIndexed"], + paddedUint64(44), + paddedUint64(27), + paddedUint64(19), + }, + Data: nil, + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexed"], + paddedUint64(44), + paddedUint64(19), + }, + Data: nil, + }, + }, + }, + + { + name: "find all EventOneIndexedWithData events from contract1 or contract2", + spec: newEthFilterBuilder(). + FromBlockEpoch(0). + AddressOneOf(contract1, contract2). + Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventOneIndexedWithData"])). + Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(44), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(46), + }, + Data: paddedUint64(12), + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + + { + name: "find all events with topic2 of 46", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic2OneOf(paddedEthHash(paddedUint64(46))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(46), + }, + Data: paddedUint64(12), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(14), + }, + Data: paddedUint64(19), + }, + }, + }, + { + name: "find all events with topic2 of 50", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic2OneOf(paddedEthHash(paddedUint64(50))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + { + name: "find all events with topic2 of 46 or 50", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic2OneOf(paddedEthHash(paddedUint64(46)), paddedEthHash(paddedUint64(50))).Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(46), + }, + Data: paddedUint64(12), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(14), + }, + Data: paddedUint64(19), + }, + { + Address: contract2, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexedWithData"], + paddedUint64(50), + }, + Data: paddedUint64(9), + }, + }, + }, + + { + name: "find all events with topic1 of EventTwoIndexedWithData and topic3 of 27", + spec: newEthFilterBuilder(). + FromBlockEpoch(0). + Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"])). + Topic3OneOf(paddedEthHash(paddedUint64(27))). + Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(46), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + }, + }, + + { + name: "find all events with topic1 of EventTwoIndexedWithData or EventOneIndexed and topic2 of 44", + spec: newEthFilterBuilder(). + FromBlockEpoch(0). + Topic1OneOf(paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"]), paddedEthHash(EventMatrixContract.Ev["EventOneIndexed"])). + Topic2OneOf(paddedEthHash(paddedUint64(44))). + Filter(), + + expected: []ExpectedEthLog{ + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(44), + paddedUint64(27), + }, + Data: paddedUint64(19), + }, + { + Address: contract1, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventOneIndexed"], + paddedUint64(44), + }, + Data: nil, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc // appease the lint despot + t.Run(tc.name, func(t *testing.T) { + res, err := client.EthGetLogs(ctx, tc.spec) + require.NoError(err) + + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, tc.expected, messages) + }) + } +} + +func TestEthGetLogsWithBlockRanges(t *testing.T) { + require := require.New(t) + kit.QuietAllLogsExcept("events", "messagepool") + + 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() + + // Set up the test fixture with a standard list of invocations + _, _, messages := invokeEventMatrix(ctx, t, client) + + // Organize expected logs into three partitions for range testing + expectedByHeight := map[abi.ChainEpoch][]ExpectedEthLog{} + distinctHeights := map[abi.ChainEpoch]bool{} + + // Select events for partitioning + for _, m := range messages { + if bytes.Equal(m.invocation.Selector, EventMatrixContract.Fn["logEventTwoIndexedWithData"]) { + addr := getContractEthAddress(ctx, t, client, m.invocation.Target) + args := unpackUint64Values(m.invocation.Data) + require.Equal(3, len(args), "logEventTwoIndexedWithData should have 3 arguments") + + distinctHeights[m.ts.Height()] = true + expectedByHeight[m.ts.Height()] = append(expectedByHeight[m.ts.Height()], ExpectedEthLog{ + Address: addr, + Topics: []ethtypes.EthBytes{ + EventMatrixContract.Ev["EventTwoIndexedWithData"], + paddedUint64(args[0]), + paddedUint64(args[1]), + }, + Data: paddedUint64(args[2]), + }) + } + } + + // Divide heights into 3 partitions, they don't have to be equal + require.True(len(distinctHeights) >= 3, "expected slice should divisible into three partitions") + heights := make([]abi.ChainEpoch, 0, len(distinctHeights)) + for h := range distinctHeights { + heights = append(heights, h) + } + sort.Slice(heights, func(i, j int) bool { + return heights[i] < heights[j] + }) + heightsPerPartition := len(heights) / 3 + + type partition struct { + start abi.ChainEpoch + end abi.ChainEpoch + expected []ExpectedEthLog + } + + var partition1, partition2, partition3 partition + + partition1.start = heights[0] + partition1.end = heights[heightsPerPartition-1] + for e := partition1.start; e <= partition1.end; e++ { + exp, ok := expectedByHeight[e] + if !ok { + continue + } + partition1.expected = append(partition1.expected, exp...) + } + t.Logf("partition1 from %d to %d with %d expected", partition1.start, partition1.end, len(partition1.expected)) + require.True(len(partition1.expected) > 0, "partition should have events") + + partition2.start = heights[heightsPerPartition] + partition2.end = heights[heightsPerPartition*2-1] + for e := partition2.start; e <= partition2.end; e++ { + exp, ok := expectedByHeight[e] + if !ok { + continue + } + partition2.expected = append(partition2.expected, exp...) + } + t.Logf("partition2 from %d to %d with %d expected", partition2.start, partition2.end, len(partition2.expected)) + require.True(len(partition2.expected) > 0, "partition should have events") + + partition3.start = heights[heightsPerPartition*2] + partition3.end = heights[len(heights)-1] + for e := partition3.start; e <= partition3.end; e++ { + exp, ok := expectedByHeight[e] + if !ok { + continue + } + partition3.expected = append(partition3.expected, exp...) + } + t.Logf("partition3 from %d to %d with %d expected", partition3.start, partition3.end, len(partition3.expected)) + require.True(len(partition3.expected) > 0, "partition should have events") + + // these are the topics we selected for partitioning earlier + topics := []ethtypes.EthHash{paddedEthHash(EventMatrixContract.Ev["EventTwoIndexedWithData"])} + + union := func(lists ...[]ExpectedEthLog) []ExpectedEthLog { + ret := []ExpectedEthLog{} + for _, list := range lists { + ret = append(ret, list...) + } + return ret + } + + testCases := []struct { + name string + spec *ethtypes.EthFilterSpec + expected []ExpectedEthLog + }{ + { + name: "find all events from genesis", + spec: newEthFilterBuilder().FromBlockEpoch(0).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected, partition3.expected), + }, + + { + name: "find all from start of partition1", + spec: newEthFilterBuilder().FromBlockEpoch(partition1.start).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected, partition3.expected), + }, + + { + name: "find all from start of partition2", + spec: newEthFilterBuilder().FromBlockEpoch(partition2.start).Topic1OneOf(topics...).Filter(), + expected: union(partition2.expected, partition3.expected), + }, + + { + name: "find all from start of partition3", + spec: newEthFilterBuilder().FromBlockEpoch(partition3.start).Topic1OneOf(topics...).Filter(), + expected: union(partition3.expected), + }, + + { + name: "find none after end of partition3", + spec: newEthFilterBuilder().FromBlockEpoch(partition3.end + 1).Topic1OneOf(topics...).Filter(), + expected: nil, + }, + + { + name: "find all events from genesis to end of partition1", + spec: newEthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected), + }, + + { + name: "find all events from genesis to end of partition2", + spec: newEthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition2.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected), + }, + + { + name: "find all events from genesis to end of partition3", + spec: newEthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition3.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected, partition3.expected), + }, + + { + name: "find none from genesis to start of partition1", + spec: newEthFilterBuilder().FromBlockEpoch(0).ToBlockEpoch(partition1.start - 1).Topic1OneOf(topics...).Filter(), + expected: nil, + }, + + { + name: "find all events in partition1", + spec: newEthFilterBuilder().FromBlockEpoch(partition1.start).ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected), + }, + + { + name: "find all events in partition2", + spec: newEthFilterBuilder().FromBlockEpoch(partition2.start).ToBlockEpoch(partition2.end).Topic1OneOf(topics...).Filter(), + expected: union(partition2.expected), + }, + + { + name: "find all events in partition3", + spec: newEthFilterBuilder().FromBlockEpoch(partition3.start).ToBlockEpoch(partition3.end).Topic1OneOf(topics...).Filter(), + expected: union(partition3.expected), + }, + + { + name: "find all events from earliest to end of partition1", + spec: newEthFilterBuilder().FromBlock("earliest").ToBlockEpoch(partition1.end).Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected), + }, + + { + name: "find all events from start of partition3 to latest", + spec: newEthFilterBuilder().FromBlockEpoch(partition3.start).ToBlock("latest").Topic1OneOf(topics...).Filter(), + expected: union(partition3.expected), + }, + + { + name: "find all events from earliest to latest", + spec: newEthFilterBuilder().FromBlock("earliest").ToBlock("latest").Topic1OneOf(topics...).Filter(), + expected: union(partition1.expected, partition2.expected, partition3.expected), + }, + } + + for _, tc := range testCases { + tc := tc // appease the lint despot + t.Run(tc.name, func(t *testing.T) { + res, err := client.EthGetLogs(ctx, tc.spec) + require.NoError(err) + + elogs, err := parseEthLogsFromFilterResult(res) + require.NoError(err) + AssertEthLogs(t, elogs, tc.expected, messages) + }) + } +} + +// ------------------------------------------------------------------------------- +// end of tests +// ------------------------------------------------------------------------------- + +type msgInTipset struct { + invocation Invocation // the solidity invocation that generated this message + msg api.Message + events []types.Event // events extracted from receipt + ts *types.TipSet + reverted bool +} + +func getContractEthAddress(ctx context.Context, t *testing.T, client *kit.TestFullNode, addr address.Address) ethtypes.EthAddress { + head, err := client.ChainHead(ctx) + require.NoError(t, err) + + actor, err := client.StateGetActor(ctx, addr, head.Key()) + require.NoError(t, err) + require.NotNil(t, actor.Address) + ethContractAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address) + require.NoError(t, err) + return ethContractAddr +} + +type Invocation struct { + Sender address.Address + Target address.Address + Selector []byte // function selector + Data []byte + MinHeight abi.ChainEpoch // minimum chain height that must be reached before invoking +} + +func invokeAndWaitUntilAllOnChain(t *testing.T, client *kit.TestFullNode, invocations []Invocation) map[ethtypes.EthHash]msgInTipset { + require := require.New(t) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + msgChan := make(chan msgInTipset, len(invocations)) waitAllCh := make(chan struct{}) + waitForFirstHeadChange := make(chan struct{}) go func() { headChangeCh, err := client.ChainNotify(ctx) require.NoError(err) - <-headChangeCh // skip hccurrent + select { + case <-ctx.Done(): + return + case <-headChangeCh: // skip hccurrent + } + + close(waitForFirstHeadChange) + + defer func() { + close(msgChan) + close(waitAllCh) + }() count := 0 for { select { + case <-ctx.Done(): + return case headChanges := <-headChangeCh: for _, change := range headChanges { if change.Type == store.HCApply || change.Type == store.HCRevert { @@ -257,14 +1355,12 @@ func TestEthNewFilterCatchAll(t *testing.T) { count += len(msgs) for _, m := range msgs { select { - case msgChan <- msgInTipset{msg: m, ts: change.Val}: + case msgChan <- msgInTipset{msg: m, ts: change.Val, reverted: change.Type == store.HCRevert}: default: } } - if count == iterations { - close(msgChan) - close(waitAllCh) + if count == len(invocations) { return } } @@ -273,62 +1369,310 @@ func TestEthNewFilterCatchAll(t *testing.T) { } }() - time.Sleep(blockTime * 6) + select { + case <-waitForFirstHeadChange: + case <-ctx.Done(): + t.Fatalf("timeout waiting for first head change") + } - for i := 0; i < iterations; i++ { - // log a four topic event with data - ret := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, []byte{0x00, 0x00, 0x00, 0x02}, nil) + eventMap := map[cid.Cid][]types.Event{} + invocationMap := map[cid.Cid]Invocation{} + for _, inv := range invocations { + if inv.MinHeight > 0 { + for { + ts, err := client.ChainHead(ctx) + require.NoError(err) + if ts.Height() >= inv.MinHeight { + break + } + select { + case <-ctx.Done(): + t.Fatalf("context cancelled") + case <-time.After(100 * time.Millisecond): + } + } + } + ret := client.EVM().InvokeSolidity(ctx, inv.Sender, inv.Target, inv.Selector, inv.Data) require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed") + + invocationMap[ret.Message] = inv + + require.NotNil(t, ret.Receipt.EventsRoot, "no event root on receipt") + + evs := client.EVM().LoadEvents(ctx, *ret.Receipt.EventsRoot) + eventMap[ret.Message] = evs } select { case <-waitAllCh: - case <-time.After(time.Minute): - t.Errorf("timeout to wait for pack messages") + case <-ctx.Done(): + t.Fatalf("timeout waiting to pack messages") } received := make(map[ethtypes.EthHash]msgInTipset) for m := range msgChan { - eh, err := ethtypes.EthHashFromCid(m.msg.Cid) + inv, ok := invocationMap[m.msg.Cid] + require.True(ok) + m.invocation = inv + + evs, ok := eventMap[m.msg.Cid] + require.True(ok) + m.events = evs + + eh, err := client.EthGetTransactionHashByCid(ctx, m.msg.Cid) require.NoError(err) - received[eh] = m + received[*eh] = m } - require.Equal(iterations, len(received), "all messages on chain") + require.Equal(len(invocations), len(received), "all messages on chain") - ts, err := client.ChainHead(ctx) - require.NoError(err) + return received +} - actor, err := client.StateGetActor(ctx, idAddr, ts.Key()) - require.NoError(err) - require.NotNil(actor.Address) - ethContractAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address) - require.NoError(err) +func invokeLogFourData(t *testing.T, client *kit.TestFullNode, iterations int) (ethtypes.EthAddress, map[ethtypes.EthHash]msgInTipset) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() - // collect filter results - res, err := client.EthGetFilterChanges(ctx, filterID) - require.NoError(err) + fromAddr, idAddr := client.EVM().DeployContractFromFilename(ctx, EventsContract.Filename) - // expect to have seen iteration number of events - require.Equal(iterations, len(res.Results)) + invocations := make([]Invocation, iterations) + for i := range invocations { + invocations[i] = Invocation{ + Sender: fromAddr, + Target: idAddr, + Selector: EventsContract.Fn["log_four_data"], + Data: nil, + } + } - topic1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x11})) - topic2 := ethtypes.EthBytes(leftpad32([]byte{0x22, 0x22})) - topic3 := ethtypes.EthBytes(leftpad32([]byte{0x33, 0x33})) - topic4 := ethtypes.EthBytes(leftpad32([]byte{0x44, 0x44})) - data1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})) + messages := invokeAndWaitUntilAllOnChain(t, client, invocations) - for _, r := range res.Results { - // since response is a union and Go doesn't support them well, go-jsonrpc won't give us typed results - rc, ok := r.(map[string]interface{}) - require.True(ok, "result type") + ethAddr := getContractEthAddress(ctx, t, client, idAddr) - elog, err := ParseEthLog(rc) - require.NoError(err) + return ethAddr, messages +} - require.Equal(ethContractAddr, elog.Address, "event address") - require.Equal(ethtypes.EthUint64(0), elog.TransactionIndex, "transaction index") // only one message per tipset +func invokeEventMatrix(ctx context.Context, t *testing.T, client *kit.TestFullNode) (ethtypes.EthAddress, ethtypes.EthAddress, map[ethtypes.EthHash]msgInTipset) { + sender1, contract1 := client.EVM().DeployContractFromFilename(ctx, EventMatrixContract.Filename) + sender2, contract2 := client.EVM().DeployContractFromFilename(ctx, EventMatrixContract.Filename) - msg, exists := received[elog.TransactionHash] + invocations := []Invocation{ + // log EventZeroData() + // topic1: hash(EventZeroData) + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventZeroData"], + Data: nil, + }, + + // log EventOneData(23) + // topic1: hash(EventOneData) + // data: 23 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneData"], + Data: packUint64Values(23), + }, + + // log EventOneIndexed(44) + // topic1: hash(EventOneIndexed) + // topic2: 44 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneIndexed"], + Data: packUint64Values(44), + }, + + // log EventTwoIndexed(44,19) from contract2 + // topic1: hash(EventTwoIndexed) + // topic2: 44 + // topic3: 19 + { + Sender: sender2, + Target: contract2, + Selector: EventMatrixContract.Fn["logEventTwoIndexed"], + Data: packUint64Values(44, 19), + }, + + // log EventOneData(44) + // topic1: hash(EventOneData) + // data: 44 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneData"], + Data: packUint64Values(44), + }, + + // log EventTwoData(555,666) + // topic1: hash(EventTwoData) + // data: 555,666 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoData"], + Data: packUint64Values(555, 666), + }, + + // log EventZeroData() from contract2 + // topic1: hash(EventZeroData) + { + Sender: sender2, + Target: contract2, + Selector: EventMatrixContract.Fn["logEventZeroData"], + Data: nil, + }, + + // log EventThreeData(1,2,3) + // topic1: hash(EventTwoData) + // data: 1,2,3 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventThreeData"], + Data: packUint64Values(1, 2, 3), + }, + + // log EventThreeIndexed(44,27,19) from contract2 + // topic1: hash(EventThreeIndexed) + // topic2: 44 + // topic3: 27 + // topic4: 19 + { + Sender: sender1, + Target: contract2, + Selector: EventMatrixContract.Fn["logEventThreeIndexed"], + Data: packUint64Values(44, 27, 19), + }, + + // log EventOneIndexedWithData(44,19) + // topic1: hash(EventOneIndexedWithData) + // topic2: 44 + // data: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneIndexedWithData"], + Data: packUint64Values(44, 19), + }, + + // log EventOneIndexedWithData(46,12) + // topic1: hash(EventOneIndexedWithData) + // topic2: 46 + // data: 12 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventOneIndexedWithData"], + Data: packUint64Values(46, 12), + }, + + // log EventTwoIndexedWithData(44,27,19) + // topic1: hash(EventTwoIndexedWithData) + // topic2: 44 + // topic3: 27 + // data: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoIndexedWithData"], + Data: packUint64Values(44, 27, 19), + }, + + // log EventThreeIndexedWithData(44,27,19,12) + // topic1: hash(EventThreeIndexedWithData) + // topic2: 44 + // topic3: 27 + // topic4: 19 + // data: 12 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventThreeIndexedWithData"], + Data: packUint64Values(44, 27, 19, 12), + }, + + // log EventOneIndexedWithData(50,9) + // topic1: hash(EventOneIndexedWithData) + // topic2: 50 + // data: 9 + { + Sender: sender2, + Target: contract2, + Selector: EventMatrixContract.Fn["logEventOneIndexedWithData"], + Data: packUint64Values(50, 9), + }, + + // log EventTwoIndexedWithData(46,27,19) + // topic1: hash(EventTwoIndexedWithData) + // topic2: 46 + // topic3: 27 + // data: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoIndexedWithData"], + Data: packUint64Values(46, 27, 19), + }, + + // log EventTwoIndexedWithData(46,14,19) + // topic1: hash(EventTwoIndexedWithData) + // topic2: 46 + // topic3: 14 + // data: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoIndexedWithData"], + Data: packUint64Values(46, 14, 19), + }, + // log EventTwoIndexed(44,19) from contract1 + // topic1: hash(EventTwoIndexed) + // topic2: 44 + // topic3: 19 + { + Sender: sender1, + Target: contract1, + Selector: EventMatrixContract.Fn["logEventTwoIndexed"], + Data: packUint64Values(40, 20), + }, + } + + messages := invokeAndWaitUntilAllOnChain(t, client, invocations) + ethAddr1 := getContractEthAddress(ctx, t, client, contract1) + ethAddr2 := getContractEthAddress(ctx, t, client, contract2) + return ethAddr1, ethAddr2, messages +} + +type ExpectedEthLog struct { + // Address is the address of the actor that produced the event log. + Address ethtypes.EthAddress `json:"address"` + + // List of topics associated with the event log. + Topics []ethtypes.EthBytes `json:"topics"` + + // Data is the value of the event log, excluding topics + Data ethtypes.EthBytes `json:"data"` +} + +func AssertEthLogs(t *testing.T, actual []*ethtypes.EthLog, expected []ExpectedEthLog, messages map[ethtypes.EthHash]msgInTipset) { + require := require.New(t) + // require.Equal(len(expected), len(actual), "number of results equal to expected") + + formatTopics := func(topics []ethtypes.EthBytes) string { + ss := make([]string, len(topics)) + for i := range topics { + ss[i] = fmt.Sprintf("%d:%x", i, topics[i]) + } + return strings.Join(ss, ",") + } + + expectedMatched := map[int]bool{} + + for _, elog := range actual { + msg, exists := messages[elog.TransactionHash] require.True(exists, "message seen on chain") tsCid, err := msg.ts.Key().Cid() @@ -337,17 +1681,113 @@ func TestEthNewFilterCatchAll(t *testing.T) { tsCidHash, err := ethtypes.EthHashFromCid(tsCid) require.NoError(err) - require.Equal(tsCidHash, elog.BlockHash, "block hash") + require.Equal(tsCidHash, elog.BlockHash, "block hash matches tipset key") - require.Equal(4, len(elog.Topics), "number of topics") - require.Equal(topic1, elog.Topics[0], "topic1") - require.Equal(topic2, elog.Topics[1], "topic2") - require.Equal(topic3, elog.Topics[2], "topic3") - require.Equal(topic4, elog.Topics[3], "topic4") + // Try and match the received log against an expected log + matched := false + LoopExpected: + for i, want := range expected { + // each expected log must match only once + if expectedMatched[i] { + continue + } - require.Equal(data1, elog.Data, "data1") + if elog.Address != want.Address { + continue + } + if len(elog.Topics) != len(want.Topics) { + continue + } + + for j := range elog.Topics { + if !bytes.Equal(elog.Topics[j], want.Topics[j]) { + continue LoopExpected + } + } + + if !bytes.Equal(elog.Data, want.Data) { + continue + } + + expectedMatched[i] = true + matched = true + break + } + + if !matched { + var buf strings.Builder + buf.WriteString(fmt.Sprintf("found unexpected log at height %d:\n", msg.ts.Height())) + buf.WriteString(fmt.Sprintf(" address: %s\n", elog.Address)) + buf.WriteString(fmt.Sprintf(" topics: %s\n", formatTopics(elog.Topics))) + buf.WriteString(fmt.Sprintf(" data: %x\n", elog.Data)) + buf.WriteString("original events from receipt were:\n") + for i, ev := range msg.events { + buf.WriteString(fmt.Sprintf("event %d\n", i)) + buf.WriteString(fmt.Sprintf(" emitter: %v\n", ev.Emitter)) + for _, en := range ev.Entries { + buf.WriteString(fmt.Sprintf(" %s=%x\n", en.Key, decodeLogBytes(en.Value))) + } + } + + t.Errorf(buf.String()) + } } + + for i := range expected { + if _, ok := expectedMatched[i]; !ok { + var buf strings.Builder + buf.WriteString(fmt.Sprintf("did not find expected log with index %d:\n", i)) + buf.WriteString(fmt.Sprintf(" address: %s\n", expected[i].Address)) + buf.WriteString(fmt.Sprintf(" topics: %s\n", formatTopics(expected[i].Topics))) + buf.WriteString(fmt.Sprintf(" data: %x\n", expected[i].Data)) + t.Errorf(buf.String()) + } + } +} + +func parseEthLogsFromSubscriptionResponses(subResponses []ethtypes.EthSubscriptionResponse) ([]*ethtypes.EthLog, error) { + elogs := make([]*ethtypes.EthLog, 0, len(subResponses)) + for i := range subResponses { + rlist, ok := subResponses[i].Result.([]interface{}) + if !ok { + return nil, xerrors.Errorf("expected subscription result to be []interface{}, but was %T", subResponses[i].Result) + } + + for _, r := range rlist { + rmap, ok := r.(map[string]interface{}) + if !ok { + return nil, xerrors.Errorf("expected subscription result entry to be map[string]interface{}, but was %T", r) + } + + elog, err := ParseEthLog(rmap) + if err != nil { + return nil, err + } + elogs = append(elogs, elog) + } + } + + return elogs, nil +} + +func parseEthLogsFromFilterResult(res *ethtypes.EthFilterResult) ([]*ethtypes.EthLog, error) { + elogs := make([]*ethtypes.EthLog, 0, len(res.Results)) + + for _, r := range res.Results { + rmap, ok := r.(map[string]interface{}) + if !ok { + return nil, xerrors.Errorf("expected filter result entry to be map[string]interface{}, but was %T", r) + } + + elog, err := ParseEthLog(rmap) + if err != nil { + return nil, err + } + elogs = append(elogs, elog) + } + + return elogs, nil } func ParseEthLog(in map[string]interface{}) (*ethtypes.EthLog, error) { @@ -463,348 +1903,7 @@ func ParseEthLog(in map[string]interface{}) (*ethtypes.EthLog, error) { return el, err } -type msgInTipset struct { - msg api.Message - ts *types.TipSet - reverted bool -} - -func invokeContractAndWaitUntilAllOnChain(t *testing.T, client *kit.TestFullNode, iterations int) (ethtypes.EthAddress, map[ethtypes.EthHash]msgInTipset) { - require := require.New(t) - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - blockTime := 100 * time.Millisecond - - // install contract - contractHex, err := os.ReadFile("contracts/events.bin") - require.NoError(err) - - contract, err := hex.DecodeString(string(contractHex)) - require.NoError(err) - - fromAddr, err := client.WalletDefaultAddress(ctx) - require.NoError(err) - - result := client.EVM().DeployContract(ctx, fromAddr, contract) - - idAddr, err := address.NewIDAddress(result.ActorID) - require.NoError(err) - t.Logf("actor ID address is %s", idAddr) - - msgChan := make(chan msgInTipset, iterations) - - waitAllCh := make(chan struct{}) - go func() { - headChangeCh, err := client.ChainNotify(ctx) - require.NoError(err) - <-headChangeCh // skip hccurrent - - count := 0 - for { - select { - case headChanges := <-headChangeCh: - for _, change := range headChanges { - if change.Type == store.HCApply || change.Type == store.HCRevert { - msgs, err := client.ChainGetMessagesInTipset(ctx, change.Val.Key()) - require.NoError(err) - - count += len(msgs) - for _, m := range msgs { - select { - case msgChan <- msgInTipset{msg: m, ts: change.Val, reverted: change.Type == store.HCRevert}: - default: - } - } - - if count == iterations { - close(msgChan) - close(waitAllCh) - return - } - } - } - } - } - }() - - time.Sleep(blockTime * 6) - - for i := 0; i < iterations; i++ { - // log a four topic event with data - ret := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, []byte{0x00, 0x00, 0x00, 0x02}, nil) - require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed") - } - - select { - case <-waitAllCh: - case <-time.After(time.Minute): - t.Errorf("timeout to wait for pack messages") - } - - received := make(map[ethtypes.EthHash]msgInTipset) - for m := range msgChan { - eh, err := ethtypes.EthHashFromCid(m.msg.Cid) - require.NoError(err) - received[eh] = m - } - require.Equal(iterations, len(received), "all messages on chain") - - head, err := client.ChainHead(ctx) - require.NoError(err) - - actor, err := client.StateGetActor(ctx, idAddr, head.Key()) - require.NoError(err) - require.NotNil(actor.Address) - ethContractAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address) - require.NoError(err) - - return ethContractAddr, received -} - -func TestEthGetLogsAll(t *testing.T) { - require := require.New(t) - - kit.QuietMiningLogs() - - blockTime := 100 * time.Millisecond - dbpath := filepath.Join(t.TempDir(), "actorevents.db") - - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.HistoricFilterAPI(dbpath)) - ens.InterconnectAll().BeginMining(blockTime) - - ethContractAddr, received := invokeContractAndWaitUntilAllOnChain(t, client, 10) - - topic1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x11})) - topic2 := ethtypes.EthBytes(leftpad32([]byte{0x22, 0x22})) - topic3 := ethtypes.EthBytes(leftpad32([]byte{0x33, 0x33})) - topic4 := ethtypes.EthBytes(leftpad32([]byte{0x44, 0x44})) - data1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})) - - pstring := func(s string) *string { return &s } - - // get all logs - res, err := client.EthGetLogs(context.Background(), ðtypes.EthFilterSpec{ - FromBlock: pstring("0x0"), - }) - require.NoError(err) - - // expect to have all messages sent - require.Equal(len(received), len(res.Results)) - - for _, r := range res.Results { - // since response is a union and Go doesn't support them well, go-jsonrpc won't give us typed results - rc, ok := r.(map[string]interface{}) - require.True(ok, "result type") - - elog, err := ParseEthLog(rc) - require.NoError(err) - - require.Equal(ethContractAddr, elog.Address, "event address") - require.Equal(ethtypes.EthUint64(0), elog.TransactionIndex, "transaction index") // only one message per tipset - - msg, exists := received[elog.TransactionHash] - require.True(exists, "message seen on chain") - - tsCid, err := msg.ts.Key().Cid() - require.NoError(err) - - tsCidHash, err := ethtypes.EthHashFromCid(tsCid) - require.NoError(err) - - require.Equal(tsCidHash, elog.BlockHash, "block hash") - - require.Equal(4, len(elog.Topics), "number of topics") - require.Equal(topic1, elog.Topics[0], "topic1") - require.Equal(topic2, elog.Topics[1], "topic2") - require.Equal(topic3, elog.Topics[2], "topic3") - require.Equal(topic4, elog.Topics[3], "topic4") - - require.Equal(data1, elog.Data, "data1") - - } -} - -func TestEthGetLogsByTopic(t *testing.T) { - require := require.New(t) - - kit.QuietMiningLogs() - - blockTime := 100 * time.Millisecond - dbpath := filepath.Join(t.TempDir(), "actorevents.db") - - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.HistoricFilterAPI(dbpath)) - ens.InterconnectAll().BeginMining(blockTime) - - invocations := 1 - - ethContractAddr, received := invokeContractAndWaitUntilAllOnChain(t, client, invocations) - - topic1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x11})) - topic2 := ethtypes.EthBytes(leftpad32([]byte{0x22, 0x22})) - topic3 := ethtypes.EthBytes(leftpad32([]byte{0x33, 0x33})) - topic4 := ethtypes.EthBytes(leftpad32([]byte{0x44, 0x44})) - data1 := ethtypes.EthBytes(leftpad32([]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})) - - // find log by known topic1 - var spec ethtypes.EthFilterSpec - err := json.Unmarshal([]byte(`{"fromBlock":"0x0","topics":["0x0000000000000000000000000000000000000000000000000000000000001111"]}`), &spec) - require.NoError(err) - - res, err := client.EthGetLogs(context.Background(), &spec) - require.NoError(err) - - require.Equal(invocations, len(res.Results)) - - for _, r := range res.Results { - // since response is a union and Go doesn't support them well, go-jsonrpc won't give us typed results - rc, ok := r.(map[string]interface{}) - require.True(ok, "result type") - - elog, err := ParseEthLog(rc) - require.NoError(err) - - require.Equal(ethContractAddr, elog.Address, "event address") - require.Equal(ethtypes.EthUint64(0), elog.TransactionIndex, "transaction index") // only one message per tipset - - msg, exists := received[elog.TransactionHash] - require.True(exists, "message seen on chain") - - tsCid, err := msg.ts.Key().Cid() - require.NoError(err) - - tsCidHash, err := ethtypes.EthHashFromCid(tsCid) - require.NoError(err) - - require.Equal(tsCidHash, elog.BlockHash, "block hash") - - require.Equal(4, len(elog.Topics), "number of topics") - require.Equal(topic1, elog.Topics[0], "topic1") - require.Equal(topic2, elog.Topics[1], "topic2") - require.Equal(topic3, elog.Topics[2], "topic3") - require.Equal(topic4, elog.Topics[3], "topic4") - - require.Equal(data1, elog.Data, "data1") - - } -} - -func TestEthSubscribeLogs(t *testing.T) { - require := require.New(t) - - kit.QuietMiningLogs() - - blockTime := 100 * time.Millisecond - client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.RealTimeFilterAPI()) - ens.InterconnectAll().BeginMining(blockTime) - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - // install contract - contractHex, err := os.ReadFile("contracts/events.bin") - require.NoError(err) - - contract, err := hex.DecodeString(string(contractHex)) - require.NoError(err) - - fromAddr, err := client.WalletDefaultAddress(ctx) - require.NoError(err) - - result := client.EVM().DeployContract(ctx, fromAddr, contract) - - idAddr, err := address.NewIDAddress(result.ActorID) - require.NoError(err) - t.Logf("actor ID address is %s", idAddr) - - // install filter - respCh, err := client.EthSubscribe(ctx, "logs", nil) - require.NoError(err) - - subResponses := []ethtypes.EthSubscriptionResponse{} - go func() { - for resp := range respCh { - subResponses = append(subResponses, resp) - } - }() - - const iterations = 10 - - type msgInTipset struct { - msg api.Message - ts *types.TipSet - } - - msgChan := make(chan msgInTipset, iterations) - - waitAllCh := make(chan struct{}) - go func() { - headChangeCh, err := client.ChainNotify(ctx) - require.NoError(err) - <-headChangeCh // skip hccurrent - - count := 0 - for { - select { - case headChanges := <-headChangeCh: - for _, change := range headChanges { - if change.Type == store.HCApply || change.Type == store.HCRevert { - msgs, err := client.ChainGetMessagesInTipset(ctx, change.Val.Key()) - require.NoError(err) - - count += len(msgs) - for _, m := range msgs { - select { - case msgChan <- msgInTipset{msg: m, ts: change.Val}: - default: - } - } - - if count == iterations { - close(msgChan) - close(waitAllCh) - return - } - } - } - } - } - }() - - time.Sleep(blockTime * 6) - - for i := 0; i < iterations; i++ { - // log a four topic event with data - ret := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, []byte{0x00, 0x00, 0x00, 0x02}, nil) - require.True(ret.Receipt.ExitCode.IsSuccess(), "contract execution failed") - } - - select { - case <-waitAllCh: - case <-time.After(time.Minute): - t.Errorf("timeout to wait for pack messages") - } - - if len(subResponses) > 0 { - ok, err := client.EthUnsubscribe(ctx, subResponses[0].SubscriptionID) - require.NoError(err) - require.True(ok, "unsubscribed") - } - - received := make(map[ethtypes.EthHash]msgInTipset) - for m := range msgChan { - eh, err := ethtypes.EthHashFromCid(m.msg.Cid) - require.NoError(err) - received[eh] = m - } - require.Equal(iterations, len(received), "all messages on chain") - - // expect to have seen all logs - require.Equal(len(received), len(subResponses)) -} - -func leftpad32(orig []byte) []byte { +func paddedEthBytes(orig []byte) ethtypes.EthBytes { needed := 32 - len(orig) if needed <= 0 { return orig @@ -813,3 +1912,136 @@ func leftpad32(orig []byte) []byte { copy(ret[needed:], orig) return ret } + +func paddedUint64(v uint64) ethtypes.EthBytes { + buf := make([]byte, 32) + binary.BigEndian.PutUint64(buf[24:], v) + return buf +} + +func paddedEthHash(orig []byte) ethtypes.EthHash { + if len(orig) > 32 { + panic("exceeds EthHash length") + } + var ret ethtypes.EthHash + needed := 32 - len(orig) + copy(ret[needed:], orig) + return ret +} + +func ethTopicHash(sig string) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(sig)) + return hasher.Sum(nil) +} + +func ethFunctionHash(sig string) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(sig)) + return hasher.Sum(nil)[:4] +} + +func packUint64Values(vals ...uint64) []byte { + ret := []byte{} + for _, v := range vals { + buf := paddedUint64(v) + ret = append(ret, buf...) + } + return ret +} + +func unpackUint64Values(data []byte) []uint64 { + if len(data)%32 != 0 { + panic("data length not a multiple of 32") + } + + var vals []uint64 + for i := 0; i < len(data); i += 32 { + v := binary.BigEndian.Uint64(data[i+24 : i+32]) + vals = append(vals, v) + } + return vals +} + +func newEthFilterBuilder() *ethFilterBuilder { return ðFilterBuilder{} } + +type ethFilterBuilder struct { + filter ethtypes.EthFilterSpec +} + +func (e *ethFilterBuilder) Filter() *ethtypes.EthFilterSpec { return &e.filter } + +func (e *ethFilterBuilder) FromBlock(v string) *ethFilterBuilder { + e.filter.FromBlock = &v + return e +} + +func (e *ethFilterBuilder) FromBlockEpoch(v abi.ChainEpoch) *ethFilterBuilder { + s := ethtypes.EthUint64(v).Hex() + e.filter.FromBlock = &s + return e +} + +func (e *ethFilterBuilder) ToBlock(v string) *ethFilterBuilder { + e.filter.ToBlock = &v + return e +} + +func (e *ethFilterBuilder) ToBlockEpoch(v abi.ChainEpoch) *ethFilterBuilder { + s := ethtypes.EthUint64(v).Hex() + e.filter.ToBlock = &s + return e +} + +func (e *ethFilterBuilder) BlockHash(h ethtypes.EthHash) *ethFilterBuilder { + e.filter.BlockHash = &h + return e +} + +func (e *ethFilterBuilder) AddressOneOf(as ...ethtypes.EthAddress) *ethFilterBuilder { + e.filter.Address = as + return e +} + +func (e *ethFilterBuilder) Topic1OneOf(hs ...ethtypes.EthHash) *ethFilterBuilder { + if len(e.filter.Topics) == 0 { + e.filter.Topics = make(ethtypes.EthTopicSpec, 1) + } + e.filter.Topics[0] = hs + return e +} + +func (e *ethFilterBuilder) Topic2OneOf(hs ...ethtypes.EthHash) *ethFilterBuilder { + for len(e.filter.Topics) < 2 { + e.filter.Topics = append(e.filter.Topics, nil) + } + e.filter.Topics[1] = hs + return e +} + +func (e *ethFilterBuilder) Topic3OneOf(hs ...ethtypes.EthHash) *ethFilterBuilder { + for len(e.filter.Topics) < 3 { + e.filter.Topics = append(e.filter.Topics, nil) + } + e.filter.Topics[2] = hs + return e +} + +func (e *ethFilterBuilder) Topic4OneOf(hs ...ethtypes.EthHash) *ethFilterBuilder { + for len(e.filter.Topics) < 4 { + e.filter.Topics = append(e.filter.Topics, nil) + } + e.filter.Topics[3] = hs + return e +} + +func decodeLogBytes(orig []byte) []byte { + if len(orig) == 0 { + return orig + } + decoded, err := cbg.ReadByteArray(bytes.NewReader(orig), uint64(len(orig))) + if err != nil { + return orig + } + return decoded +} diff --git a/itests/eth_hash_lookup_test.go b/itests/eth_hash_lookup_test.go new file mode 100644 index 000000000..37d069796 --- /dev/null +++ b/itests/eth_hash_lookup_test.go @@ -0,0 +1,510 @@ +package itests + +import ( + "context" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/itests/kit" +) + +// TestTransactionHashLookup tests to see if lotus correctly stores a mapping from ethereum transaction hash to +// Filecoin Message Cid +func TestTransactionHashLookup(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + // now deploy a contract from the embryo, and validate it went well + tx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.Zero(), + Nonce: 0, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: contract, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, key.PrivateKey) + + rawTxHash, err := tx.TxHash() + require.NoError(t, err) + + hash := client.EVM().SubmitTransaction(ctx, &tx) + require.Equal(t, rawTxHash, hash) + + mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, mpoolTx.Hash) + + // Wait for message to land on chain + var receipt *api.EthTxReceipt + for i := 0; i < 20; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + if err != nil || receipt == nil { + time.Sleep(blocktime) + continue + } + break + } + require.NoError(t, err) + require.NotNil(t, receipt) + + // Verify that the chain transaction now has new fields set. + chainTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, chainTx.Hash) + + // require that the hashes are identical + require.Equal(t, hash, chainTx.Hash) + require.NotNil(t, chainTx.BlockNumber) + require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) + require.NotNil(t, chainTx.BlockHash) + require.NotEmpty(t, *chainTx.BlockHash) + require.NotNil(t, chainTx.TransactionIndex) + require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction +} + +// TestTransactionHashLookupBlsFilecoinMessage tests to see if lotus can find a BLS Filecoin Message using the transaction hash +func TestTransactionHashLookupBlsFilecoinMessage(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTBLS) + require.NoError(t, err) + + toSend := big.Div(bal, big.NewInt(2)) + msg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: toSend, + } + + sm, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + hash, err := ethtypes.EthHashFromCid(sm.Message.Cid()) + require.NoError(t, err) + + mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, mpoolTx.Hash) + + // Wait for message to land on chain + var receipt *api.EthTxReceipt + for i := 0; i < 20; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + if err != nil || receipt == nil { + time.Sleep(blocktime) + continue + } + break + } + require.NoError(t, err) + require.NotNil(t, receipt) + require.Equal(t, hash, receipt.TransactionHash) + + // Verify that the chain transaction now has new fields set. + chainTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, chainTx.Hash) + + // require that the hashes are identical + require.Equal(t, hash, chainTx.Hash) + require.NotNil(t, chainTx.BlockNumber) + require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) + require.NotNil(t, chainTx.BlockHash) + require.NotEmpty(t, *chainTx.BlockHash) + require.NotNil(t, chainTx.TransactionIndex) + require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction +} + +// TestTransactionHashLookupSecpFilecoinMessage tests to see if lotus can find a Secp Filecoin Message using the transaction hash +func TestTransactionHashLookupSecpFilecoinMessage(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + + toSend := big.Div(bal, big.NewInt(2)) + setupMsg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: toSend, + } + + setupSmsg, err := client.MpoolPushMessage(ctx, setupMsg, nil) + require.NoError(t, err) + + _, err = client.StateWaitMsg(ctx, setupSmsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + // Send message for secp account + secpMsg := &types.Message{ + From: addr, + To: client.DefaultKey.Address, + Value: big.Div(toSend, big.NewInt(2)), + } + + secpSmsg, err := client.MpoolPushMessage(ctx, secpMsg, nil) + require.NoError(t, err) + + hash, err := ethtypes.EthHashFromCid(secpSmsg.Cid()) + require.NoError(t, err) + + mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, mpoolTx.Hash) + + _, err = client.StateWaitMsg(ctx, secpSmsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + receipt, err := client.EthGetTransactionReceipt(ctx, hash) + require.NoError(t, err) + require.NotNil(t, receipt) + require.Equal(t, hash, receipt.TransactionHash) + + // Verify that the chain transaction now has new fields set. + chainTx, err := client.EthGetTransactionByHash(ctx, &hash) + require.NoError(t, err) + require.Equal(t, hash, chainTx.Hash) + + // require that the hashes are identical + require.Equal(t, hash, chainTx.Hash) + require.NotNil(t, chainTx.BlockNumber) + require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0)) + require.NotNil(t, chainTx.BlockHash) + require.NotEmpty(t, *chainTx.BlockHash) + require.NotNil(t, chainTx.TransactionIndex) + require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction +} + +// TestTransactionHashLookupSecpFilecoinMessage tests to see if lotus can find a Secp Filecoin Message using the transaction hash +func TestTransactionHashLookupNonexistentMessage(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + cid := build.MustParseCid("bafk2bzacecapjnxnyw4talwqv5ajbtbkzmzqiosztj5cb3sortyp73ndjl76e") + + // We shouldn't be able to return a hash for this fake cid + chainHash, err := client.EthGetTransactionHashByCid(ctx, cid) + require.NoError(t, err) + require.Nil(t, chainHash) + + calculatedHash, err := ethtypes.EthHashFromCid(cid) + require.NoError(t, err) + + // We shouldn't be able to return a cid for this fake hash + chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &calculatedHash) + require.NoError(t, err) + require.Nil(t, chainCid) +} + +func TestEthGetMessageCidByTransactionHashEthTx(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // install contract + contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + // create a new Ethereum account + key, ethAddr, deployer := client.EVM().NewAccount() + + // send some funds to the f410 address + kit.SendFunds(ctx, t, client, deployer, types.FromFil(10)) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: ðAddr, + Data: contract, + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + // now deploy a contract from the embryo, and validate it went well + tx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.Zero(), + Nonce: 0, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + Input: contract, + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, key.PrivateKey) + + sender, err := tx.Sender() + require.NoError(t, err) + + unsignedMessage, err := tx.ToUnsignedMessage(sender) + require.NoError(t, err) + + rawTxHash, err := tx.TxHash() + require.NoError(t, err) + + hash := client.EVM().SubmitTransaction(ctx, &tx) + require.Equal(t, rawTxHash, hash) + + mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, mpoolCid) + + mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, mpoolTx) + require.Equal(t, *unsignedMessage, *mpoolTx) + + // Wait for message to land on chain + var receipt *api.EthTxReceipt + for i := 0; i < 20; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + if err != nil || receipt == nil { + time.Sleep(blocktime) + continue + } + break + } + require.NoError(t, err) + require.NotNil(t, receipt) + + chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, chainCid) + + chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, chainTx) + require.Equal(t, *unsignedMessage, *chainTx) +} + +func TestEthGetMessageCidByTransactionHashSecp(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + + toSend := big.Div(bal, big.NewInt(2)) + setupMsg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: toSend, + } + + setupSmsg, err := client.MpoolPushMessage(ctx, setupMsg, nil) + require.NoError(t, err) + + _, err = client.StateWaitMsg(ctx, setupSmsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + // Send message for secp account + secpMsg := &types.Message{ + From: addr, + To: client.DefaultKey.Address, + Value: big.Div(toSend, big.NewInt(2)), + } + + secpSmsg, err := client.MpoolPushMessage(ctx, secpMsg, nil) + require.NoError(t, err) + + hash, err := ethtypes.EthHashFromCid(secpSmsg.Cid()) + require.NoError(t, err) + + mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, mpoolCid) + + mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, mpoolTx) + require.Equal(t, secpSmsg.Message, *mpoolTx) + + _, err = client.StateWaitMsg(ctx, secpSmsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, chainCid) + + chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, chainTx) + require.Equal(t, secpSmsg.Message, *chainTx) +} + +func TestEthGetMessageCidByTransactionHashBLS(t *testing.T) { + kit.QuietMiningLogs() + + blocktime := 1 * time.Second + client, _, ens := kit.EnsembleMinimal( + t, + kit.MockProofs(), + kit.ThroughRPC(), + ) + ens.InterconnectAll().BeginMining(blocktime) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // get the existing balance from the default wallet to then split it. + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + + // create a new address where to send funds. + addr, err := client.WalletNew(ctx, types.KTBLS) + require.NoError(t, err) + + toSend := big.Div(bal, big.NewInt(2)) + msg := &types.Message{ + From: client.DefaultKey.Address, + To: addr, + Value: toSend, + } + + sm, err := client.MpoolPushMessage(ctx, msg, nil) + require.NoError(t, err) + + hash, err := ethtypes.EthHashFromCid(sm.Cid()) + require.NoError(t, err) + + mpoolCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, mpoolCid) + + mpoolTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, mpoolTx) + require.Equal(t, sm.Message, *mpoolTx) + + _, err = client.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + + chainCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash) + require.NoError(t, err) + require.NotNil(t, chainCid) + + chainTx, err := client.ChainGetMessage(ctx, *mpoolCid) + require.NoError(t, err) + require.NotNil(t, chainTx) + require.Equal(t, sm.Message, *chainTx) +} diff --git a/itests/fevm_events_test.go b/itests/fevm_events_test.go index 30dd7015f..079a7a699 100644 --- a/itests/fevm_events_test.go +++ b/itests/fevm_events_test.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/itests/kit" ) @@ -45,22 +46,22 @@ func TestFEVMEvents(t *testing.T) { require.NoError(err) t.Logf("actor ID address is %s", idAddr) - // var ( - // earliest = "earliest" - // latest = "latest" - // ) - // - // // Install a filter. - // filter, err := client.EthNewFilter(ctx, &api.EthFilterSpec{ - // FromBlock: &earliest, - // ToBlock: &latest, - // }) - // require.NoError(err) - // - // // No logs yet. - // res, err := client.EthGetFilterLogs(ctx, filter) - // require.NoError(err) - // require.Empty(res.NewLogs) + var ( + earliest = "earliest" + latest = "latest" + ) + + // Install a filter. + filter, err := client.EthNewFilter(ctx, ðtypes.EthFilterSpec{ + FromBlock: &earliest, + ToBlock: &latest, + }) + require.NoError(err) + + // No logs yet. + res, err := client.EthGetFilterLogs(ctx, filter) + require.NoError(err) + require.Empty(res.Results) // log a zero topic event with data ret := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, []byte{0x00, 0x00, 0x00, 0x00}, nil) diff --git a/itests/fevm_test.go b/itests/fevm_test.go index af29f83a8..e05b0e2cc 100644 --- a/itests/fevm_test.go +++ b/itests/fevm_test.go @@ -1,67 +1,64 @@ package itests import ( - "bytes" "context" "encoding/hex" - "os" "testing" "time" "github.com/stretchr/testify/require" - cbg "github.com/whyrusleeping/cbor-gen" "github.com/filecoin-project/go-address" builtintypes "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/go-state-types/manifest" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/itests/kit" ) -// TestFEVMBasic does a basic fevm contract installation and invocation -func TestFEVMBasic(t *testing.T) { - // TODO the contract installation and invocation can be lifted into utility methods - // He who writes the second test, shall do that. - kit.QuietMiningLogs() +// convert a simple byte array into input data which is a left padded 32 byte array +func inputDataFromArray(input []byte) []byte { + inputData := make([]byte, 32) + copy(inputData[32-len(input):], input[:]) + return inputData +} +// convert a "from" address into input data which is a left padded 32 byte array +func inputDataFromFrom(ctx context.Context, t *testing.T, client *kit.TestFullNode, from address.Address) []byte { + fromId, err := client.StateLookupID(ctx, from, types.EmptyTSK) + require.NoError(t, err) + + senderEthAddr, err := ethtypes.EthAddressFromFilecoinAddress(fromId) + require.NoError(t, err) + inputData := make([]byte, 32) + copy(inputData[32-len(senderEthAddr):], senderEthAddr[:]) + return inputData +} + +func setupFEVMTest(t *testing.T) (context.Context, context.CancelFunc, *kit.TestFullNode) { + 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) + return ctx, cancel, client +} + +// TestFEVMBasic does a basic fevm contract installation and invocation +func TestFEVMBasic(t *testing.T) { + + ctx, cancel, client := setupFEVMTest(t) defer cancel() + filename := "contracts/SimpleCoin.hex" // install contract - contractHex, err := os.ReadFile("contracts/SimpleCoin.hex") - require.NoError(t, err) - - contract, err := hex.DecodeString(string(contractHex)) - require.NoError(t, err) - - fromAddr, err := client.WalletDefaultAddress(ctx) - require.NoError(t, err) - - result := client.EVM().DeployContract(ctx, fromAddr, contract) - - idAddr, err := address.NewIDAddress(result.ActorID) - require.NoError(t, err) - t.Logf("actor ID address is %s", idAddr) + fromAddr, idAddr := client.EVM().DeployContractFromFilename(ctx, filename) // invoke the contract with owner { - entryPoint, err := hex.DecodeString("f8b2cb4f") - require.NoError(t, err) - - inputData, err := hex.DecodeString("000000000000000000000000ff00000000000000000000000000000000000064") - require.NoError(t, err) - - wait := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData) - - require.True(t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed") - - result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return))) - require.NoError(t, err) + inputData := inputDataFromFrom(ctx, t, client, fromAddr) + result := client.EVM().InvokeContractByFuncName(ctx, fromAddr, idAddr, "getBalance(address)", inputData) expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000002710") require.NoError(t, err) @@ -70,34 +67,19 @@ func TestFEVMBasic(t *testing.T) { // invoke the contract with non owner { - entryPoint, err := hex.DecodeString("f8b2cb4f") - require.NoError(t, err) - - inputData, err := hex.DecodeString("000000000000000000000000ff00000000000000000000000000000000000065") - require.NoError(t, err) - - wait := client.EVM().InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData) - require.True(t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed") - - result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return))) - require.NoError(t, err) + inputData := inputDataFromFrom(ctx, t, client, fromAddr) + inputData[31]++ // change the pub address to one that has 0 balance by incrementing the last byte of the address + result := client.EVM().InvokeContractByFuncName(ctx, fromAddr, idAddr, "getBalance(address)", inputData) expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") require.NoError(t, err) require.Equal(t, result, expectedResult) - } } // TestFEVMETH0 tests that the ETH0 actor is in genesis func TestFEVMETH0(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) + ctx, cancel, client := setupFEVMTest(t) defer cancel() eth0id, err := address.NewIDAddress(1001) @@ -112,3 +94,48 @@ func TestFEVMETH0(t *testing.T) { require.NoError(t, err) require.Equal(t, *act.Address, eth0Addr) } + +// TestFEVMDelegateCall deploys two contracts and makes a delegate call transaction +func TestFEVMDelegateCall(t *testing.T) { + + ctx, cancel, client := setupFEVMTest(t) + defer cancel() + + //install contract Actor + filenameActor := "contracts/DelegatecallActor.hex" + fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) + //install contract Storage + filenameStorage := "contracts/DelegatecallStorage.hex" + fromAddrStorage, storageAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) + require.Equal(t, fromAddr, fromAddrStorage) + + //call Contract Storage which makes a delegatecall to contract Actor + //this contract call sets the "counter" variable to 7, from default value 0 + + inputDataContract := inputDataFromFrom(ctx, t, client, actorAddr) + inputDataValue := inputDataFromArray([]byte{7}) + inputData := append(inputDataContract, inputDataValue...) + + //verify that the returned value of the call to setvars is 7 + result := client.EVM().InvokeContractByFuncName(ctx, fromAddr, storageAddr, "setVars(address,uint256)", inputData) + expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000007") + require.NoError(t, err) + require.Equal(t, result, expectedResult) + + //test the value is 7 via calling the getter + result = client.EVM().InvokeContractByFuncName(ctx, fromAddr, storageAddr, "getCounter()", []byte{}) + require.Equal(t, result, expectedResult) + + //test the value is 0 via calling the getter on the Actor contract + result = client.EVM().InvokeContractByFuncName(ctx, fromAddr, actorAddr, "getCounter()", []byte{}) + expectedResultActor, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + require.Equal(t, result, expectedResultActor) +} + +func TestEVMRpcDisable(t *testing.T) { + client, _, _ := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC(), kit.DisableEthRPC()) + + _, err := client.EthBlockNumber(context.Background()) + require.ErrorContains(t, err, "module disabled, enable with Fevm.EnableEthRPC") +} diff --git a/itests/kit/evm.go b/itests/kit/evm.go index 0c12229f5..46aaf52db 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -4,7 +4,9 @@ import ( "bytes" "context" "encoding/binary" + "encoding/hex" "fmt" + "os" "github.com/ipfs/go-cid" "github.com/multiformats/go-varint" @@ -77,6 +79,26 @@ func (e *EVM) DeployContract(ctx context.Context, sender address.Address, byteco return result } +func (e *EVM) DeployContractFromFilename(ctx context.Context, binFilename string) (address.Address, address.Address) { + contractHex, err := os.ReadFile(binFilename) + require.NoError(e.t, err) + + // strip any trailing newlines from the file + contractHex = bytes.TrimRight(contractHex, "\n") + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(e.t, err) + + fromAddr, err := e.WalletDefaultAddress(ctx) + require.NoError(e.t, err) + + result := e.DeployContract(ctx, fromAddr, contract) + + idAddr, err := address.NewIDAddress(result.ActorID) + require.NoError(e.t, err) + return fromAddr, idAddr +} + func (e *EVM) InvokeSolidity(ctx context.Context, sender address.Address, target address.Address, selector []byte, inputData []byte) *api.MsgLookup { require := require.New(e.t) @@ -212,6 +234,23 @@ func (e *EVM) ComputeContractAddress(deployer ethtypes.EthAddress, nonce uint64) return *(*ethtypes.EthAddress)(hasher.Sum(nil)[12:]) } +func (e *EVM) InvokeContractByFuncName(ctx context.Context, fromAddr address.Address, idAddr address.Address, funcSignature string, inputData []byte) []byte { + entryPoint := CalcFuncSignature(funcSignature) + wait := e.InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData) + require.True(e.t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed") + result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return))) + require.NoError(e.t, err) + return result +} + +// function signatures are the first 4 bytes of the hash of the function name and types +func CalcFuncSignature(funcName string) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write([]byte(funcName)) + hash := hasher.Sum(nil) + return hash[:4] +} + // TODO: cleanup and put somewhere reusable. type apiIpldStore struct { ctx context.Context diff --git a/itests/kit/log.go b/itests/kit/log.go index beac3895c..0da9adfeb 100644 --- a/itests/kit/log.go +++ b/itests/kit/log.go @@ -1,6 +1,9 @@ package kit import ( + "io" + "log" + logging "github.com/ipfs/go-log/v2" "github.com/filecoin-project/lotus/lib/lotuslog" @@ -20,3 +23,13 @@ func QuietMiningLogs() { _ = logging.SetLogLevel("rpc", "ERROR") _ = logging.SetLogLevel("dht/RtRefreshManager", "ERROR") } + +func QuietAllLogsExcept(names ...string) { + log.SetOutput(io.Discard) // suppress LogDatastore messages + + lotuslog.SetupLogLevels() + logging.SetAllLoggers(logging.LevelError) + for _, name := range names { + _ = logging.SetLogLevel(name, "INFO") + } +} diff --git a/itests/kit/node_full.go b/itests/kit/node_full.go index 12db91c68..4546f5a03 100644 --- a/itests/kit/node_full.go +++ b/itests/kit/node_full.go @@ -135,13 +135,21 @@ func HeightAtLeast(target abi.ChainEpoch) ChainPredicate { } } -// BlockMinedBy returns a ChainPredicate that is satisfied when we observe the -// first block mined by the specified miner. -func BlockMinedBy(miner address.Address) ChainPredicate { +// BlocksMinedByAll returns a ChainPredicate that is satisfied when we observe a +// tipset including blocks from all the specified miners, in no particular order. +func BlocksMinedByAll(miner ...address.Address) ChainPredicate { return func(ts *types.TipSet) bool { + seen := make([]bool, len(miner)) + var done int for _, b := range ts.Blocks() { - if b.Miner == miner { - return true + for i, m := range miner { + if b.Miner != m || seen[i] { + continue + } + seen[i] = true + if done++; done == len(miner) { + return true + } } } return false diff --git a/itests/kit/node_opts.go b/itests/kit/node_opts.go index a220a0d1b..5d418c5be 100644 --- a/itests/kit/node_opts.go +++ b/itests/kit/node_opts.go @@ -58,6 +58,15 @@ var DefaultNodeOpts = nodeOpts{ sectors: DefaultPresealsPerBootstrapMiner, sectorSize: abi.SectorSize(2 << 10), // 2KiB. + cfgOpts: []CfgOption{ + func(cfg *config.FullNode) error { + // test defaults + + cfg.Fevm.EnableEthRPC = true + return nil + }, + }, + workerTasks: []sealtasks.TaskType{sealtasks.TTFetch, sealtasks.TTCommit1, sealtasks.TTFinalize, sealtasks.TTFinalizeUnsealed}, workerStorageOpt: func(store paths.Store) paths.Store { return store }, } @@ -281,18 +290,16 @@ func SplitstoreMessges() NodeOpt { }) } -func RealTimeFilterAPI() NodeOpt { +func WithEthRPC() NodeOpt { return WithCfgOpt(func(cfg *config.FullNode) error { - cfg.ActorEvent.EnableRealTimeFilterAPI = true + cfg.Fevm.EnableEthRPC = true return nil }) } -func HistoricFilterAPI(dbpath string) NodeOpt { +func DisableEthRPC() NodeOpt { return WithCfgOpt(func(cfg *config.FullNode) error { - cfg.ActorEvent.EnableRealTimeFilterAPI = true - cfg.ActorEvent.EnableHistoricFilterAPI = true - cfg.ActorEvent.ActorEventDatabasePath = dbpath + cfg.Fevm.EnableEthRPC = false return nil }) } diff --git a/itests/splitstore_test.go b/itests/splitstore_test.go index b5339d24c..4bbe56536 100644 --- a/itests/splitstore_test.go +++ b/itests/splitstore_test.go @@ -351,13 +351,11 @@ func splitStorePruneIndex(ctx context.Context, t *testing.T, n *kit.TestFullNode } func ipldExists(ctx context.Context, t *testing.T, c cid.Cid, n *kit.TestFullNode) bool { - _, err := n.ChainReadObj(ctx, c) - if ipld.IsNotFound(err) { - return false - } else if err != nil { - t.Fatalf("ChainReadObj failure on existence check: %s", err) + found, err := n.ChainHasObj(ctx, c) + if err != nil { + t.Fatalf("ChainHasObj failure: %s", err) } - return true + return found } // Create on chain unreachable garbage for a network to exercise splitstore @@ -414,12 +412,10 @@ func (g *Garbager) Exists(ctx context.Context, c cid.Cid) bool { return false } else if err != nil { g.t.Fatalf("ChainReadObj failure on existence check: %s", err) + return false // unreachable } else { return true } - - g.t.Fatal("unreachable") - return false } func (g *Garbager) newPeerID(ctx context.Context) abi.ChainEpoch { diff --git a/lib/sigs/delegated/init.go b/lib/sigs/delegated/init.go index 4db83b3e7..81886ceaa 100644 --- a/lib/sigs/delegated/init.go +++ b/lib/sigs/delegated/init.go @@ -68,7 +68,7 @@ func (delegatedSigner) Verify(sig []byte, a address.Address, msg []byte) error { } if maybeaddr != a { - return fmt.Errorf("signature did not match") + return fmt.Errorf("signature did not match maybeaddr: %s, signer: %s", maybeaddr, a) } return nil diff --git a/node/builder_chain.go b/node/builder_chain.go index 6c4c6be97..07b082cd2 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -161,7 +161,6 @@ var ChainNode = Options( Override(new(messagepool.Provider), messagepool.NewProvider), Override(new(messagepool.MpoolNonceAPI), From(new(*messagepool.MessagePool))), Override(new(full.ChainModuleAPI), From(new(full.ChainModule))), - Override(new(full.EthModuleAPI), From(new(full.EthModule))), Override(new(full.GasModuleAPI), From(new(full.GasModule))), Override(new(full.MpoolModuleAPI), From(new(full.MpoolModule))), Override(new(full.StateModuleAPI), From(new(full.StateModule))), @@ -260,7 +259,10 @@ func ConfigFullNode(c interface{}) Option { // Actor event filtering support Override(new(events.EventAPI), From(new(modules.EventAPI))), // in lite-mode Eth event api is provided by gateway - ApplyIf(isFullNode, Override(new(full.EthEventAPI), modules.EthEventAPI(cfg.ActorEvent))), + ApplyIf(isFullNode, Override(new(full.EthEventAPI), modules.EthEventAPI(cfg.Fevm))), + + If(cfg.Fevm.EnableEthRPC, Override(new(full.EthModuleAPI), modules.EthModuleAPI(cfg.Fevm))), + If(!cfg.Fevm.EnableEthRPC, Override(new(full.EthModuleAPI), &full.EthModuleDummy{})), ) } diff --git a/node/config/def.go b/node/config/def.go index 7540aa3f7..9a24449ba 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -99,13 +99,17 @@ func DefaultFullNode() *FullNode { }, }, Cluster: *DefaultUserRaftConfig(), - ActorEvent: ActorEventConfig{ - EnableRealTimeFilterAPI: false, - EnableHistoricFilterAPI: false, - FilterTTL: Duration(time.Hour * 24), - MaxFilters: 100, - MaxFilterResults: 10000, - MaxFilterHeightRange: 2880, // conservative limit of one day + Fevm: FevmConfig{ + EnableEthRPC: false, + EthTxHashMappingLifetimeDays: 0, + Events: Events{ + DisableRealTimeFilterAPI: false, + DisableHistoricFilterAPI: false, + FilterTTL: Duration(time.Hour * 24), + MaxFilters: 100, + MaxFilterResults: 10000, + MaxFilterHeightRange: 2880, // conservative limit of one day + }, }, } } diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 0da9c7853..8b79bed4f 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -29,56 +29,6 @@ var Doc = map[string][]DocField{ Comment: ``, }, }, - "ActorEventConfig": []DocField{ - { - Name: "EnableRealTimeFilterAPI", - Type: "bool", - - Comment: `EnableRealTimeFilterAPI enables APIs that can create and query filters for actor events as they are emitted.`, - }, - { - Name: "EnableHistoricFilterAPI", - Type: "bool", - - Comment: `EnableHistoricFilterAPI enables APIs that can create and query filters for actor events that occurred in the past. -A queryable index of events will be maintained.`, - }, - { - Name: "FilterTTL", - Type: "Duration", - - Comment: `FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than -this time become eligible for automatic deletion.`, - }, - { - Name: "MaxFilters", - Type: "int", - - Comment: `MaxFilters specifies the maximum number of filters that may exist at any one time.`, - }, - { - Name: "MaxFilterResults", - Type: "int", - - Comment: `MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter.`, - }, - { - Name: "MaxFilterHeightRange", - Type: "uint64", - - Comment: `MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying -the entire chain)`, - }, - { - Name: "ActorEventDatabasePath", - Type: "string", - - Comment: `ActorEventDatabasePath is the full path to a sqlite database that will be used to index actor events to -support the historic filter APIs. If the database does not exist it will be created. The directory containing -the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as -relative to the CWD (current working directory).`, - }, - }, "Backup": []DocField{ { Name: "DisableMetadataLog", @@ -391,6 +341,59 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# Comment: ``, }, }, + "Events": []DocField{ + { + Name: "DisableRealTimeFilterAPI", + Type: "bool", + + Comment: `EnableEthRPC enables APIs that +DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. +The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag.`, + }, + { + Name: "DisableHistoricFilterAPI", + Type: "bool", + + Comment: `DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events +that occurred in the past. HistoricFilterAPI maintains a queryable index of events. +The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag.`, + }, + { + Name: "FilterTTL", + Type: "Duration", + + Comment: `FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than +this time become eligible for automatic deletion.`, + }, + { + Name: "MaxFilters", + Type: "int", + + Comment: `MaxFilters specifies the maximum number of filters that may exist at any one time.`, + }, + { + Name: "MaxFilterResults", + Type: "int", + + Comment: `MaxFilterResults specifies the maximum number of results that can be accumulated by an actor event filter.`, + }, + { + Name: "MaxFilterHeightRange", + Type: "uint64", + + Comment: `MaxFilterHeightRange specifies the maximum range of heights that can be used in a filter (to avoid querying +the entire chain)`, + }, + { + Name: "DatabasePath", + Type: "string", + + Comment: `DatabasePath is the full path to a sqlite database that will be used to index actor events to +support the historic filter APIs. If the database does not exist it will be created. The directory containing +the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as +relative to the CWD (current working directory).`, + }, + }, "FeeConfig": []DocField{ { Name: "DefaultMaxFee", @@ -399,6 +402,28 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# Comment: ``, }, }, + "FevmConfig": []DocField{ + { + Name: "EnableEthRPC", + Type: "bool", + + Comment: `EnableEthRPC enables eth_ rpc, and enables storing a mapping of eth transaction hashes to filecoin message Cids. +This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above.`, + }, + { + Name: "EthTxHashMappingLifetimeDays", + Type: "int", + + Comment: `EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days +Set to 0 to keep all mappings`, + }, + { + Name: "Events", + Type: "Events", + + Comment: ``, + }, + }, "FullNode": []DocField{ { Name: "Client", @@ -431,8 +456,8 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# Comment: ``, }, { - Name: "ActorEvent", - Type: "ActorEventConfig", + Name: "Fevm", + Type: "FevmConfig", Comment: ``, }, diff --git a/node/config/types.go b/node/config/types.go index b0f9b63c0..690e8caee 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -27,7 +27,7 @@ type FullNode struct { Fees FeeConfig Chainstore Chainstore Cluster UserRaftConfig - ActorEvent ActorEventConfig + Fevm FevmConfig } // // Common @@ -659,13 +659,28 @@ type UserRaftConfig struct { Tracing bool } -type ActorEventConfig struct { - // EnableRealTimeFilterAPI enables APIs that can create and query filters for actor events as they are emitted. - EnableRealTimeFilterAPI bool +type FevmConfig struct { + // EnableEthRPC enables eth_ rpc, and enables storing a mapping of eth transaction hashes to filecoin message Cids. + // This will also enable the RealTimeFilterAPI and HistoricFilterAPI by default, but they can be disabled by config options above. + EnableEthRPC bool - // EnableHistoricFilterAPI enables APIs that can create and query filters for actor events that occurred in the past. - // A queryable index of events will be maintained. - EnableHistoricFilterAPI bool + // EthTxHashMappingLifetimeDays the transaction hash lookup database will delete mappings that have been stored for more than x days + // Set to 0 to keep all mappings + EthTxHashMappingLifetimeDays int + + Events Events +} + +type Events struct { + // EnableEthRPC enables APIs that + // DisableRealTimeFilterAPI will disable the RealTimeFilterAPI that can create and query filters for actor events as they are emitted. + // The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + DisableRealTimeFilterAPI bool + + // DisableHistoricFilterAPI will disable the HistoricFilterAPI that can create and query filters for actor events + // that occurred in the past. HistoricFilterAPI maintains a queryable index of events. + // The API is enabled when EnableEthRPC is true, but can be disabled selectively with this flag. + DisableHistoricFilterAPI bool // FilterTTL specifies the time to live for actor event filters. Filters that haven't been accessed longer than // this time become eligible for automatic deletion. @@ -681,11 +696,11 @@ type ActorEventConfig struct { // the entire chain) MaxFilterHeightRange uint64 - // ActorEventDatabasePath is the full path to a sqlite database that will be used to index actor events to + // DatabasePath is the full path to a sqlite database that will be used to index actor events to // support the historic filter APIs. If the database does not exist it will be created. The directory containing // the database must already exist and be writeable. If a relative path is provided here, sqlite treats it as // relative to the CWD (current working directory). - ActorEventDatabasePath string + DatabasePath string // Others, not implemented yet: // Set a limit on the number of active websocket subscriptions (may be zero) diff --git a/node/impl/full/dummy.go b/node/impl/full/dummy.go index 865e14c9a..aa1450212 100644 --- a/node/impl/full/dummy.go +++ b/node/impl/full/dummy.go @@ -4,106 +4,118 @@ import ( "context" "errors" + "github.com/ipfs/go-cid" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types/ethtypes" ) -var ErrImplementMe = errors.New("Not implemented yet") +var ErrModuleDisabled = errors.New("module disabled, enable with Fevm.EnableEthRPC / LOTUS_FEVM_ENABLEETHPRC") type EthModuleDummy struct{} +func (e *EthModuleDummy) EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) { + return nil, ErrModuleDisabled +} + func (e *EthModuleDummy) EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) { - return 0, ErrImplementMe + return 0, ErrModuleDisabled } func (e *EthModuleDummy) EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) { - return nil, ErrImplementMe + return nil, ErrModuleDisabled } func (e *EthModuleDummy) EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) { - return 0, ErrImplementMe + return 0, ErrModuleDisabled } func (e *EthModuleDummy) EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) { - return 0, ErrImplementMe + return 0, ErrModuleDisabled } func (e *EthModuleDummy) EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) { - return ethtypes.EthBlock{}, ErrImplementMe + return ethtypes.EthBlock{}, ErrModuleDisabled } func (e *EthModuleDummy) EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) { - return ethtypes.EthBlock{}, ErrImplementMe + return ethtypes.EthBlock{}, ErrModuleDisabled } func (e *EthModuleDummy) EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) { - return nil, ErrImplementMe + return nil, ErrModuleDisabled } func (e *EthModuleDummy) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) { - return 0, ErrImplementMe + return 0, ErrModuleDisabled } func (e *EthModuleDummy) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) { - return nil, ErrImplementMe + return nil, ErrModuleDisabled } func (e *EthModuleDummy) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { - return ethtypes.EthTx{}, ErrImplementMe + return ethtypes.EthTx{}, ErrModuleDisabled } func (e *EthModuleDummy) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) { - return ethtypes.EthTx{}, ErrImplementMe + return ethtypes.EthTx{}, ErrModuleDisabled } func (e *EthModuleDummy) EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) { - return nil, ErrImplementMe + return nil, ErrModuleDisabled } func (e *EthModuleDummy) EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) { - return nil, ErrImplementMe + return nil, ErrModuleDisabled } func (e *EthModuleDummy) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) { - return ethtypes.EthBigIntZero, ErrImplementMe + return ethtypes.EthBigIntZero, ErrModuleDisabled } func (e *EthModuleDummy) EthFeeHistory(ctx context.Context, blkCount ethtypes.EthUint64, newestBlk string, rewardPercentiles []float64) (ethtypes.EthFeeHistory, error) { - return ethtypes.EthFeeHistory{}, ErrImplementMe + return ethtypes.EthFeeHistory{}, ErrModuleDisabled } func (e *EthModuleDummy) EthChainId(ctx context.Context) (ethtypes.EthUint64, error) { - return 0, ErrImplementMe + return 0, ErrModuleDisabled } func (e *EthModuleDummy) NetVersion(ctx context.Context) (string, error) { - return "", ErrImplementMe + return "", ErrModuleDisabled } func (e *EthModuleDummy) NetListening(ctx context.Context) (bool, error) { - return false, ErrImplementMe + return false, ErrModuleDisabled } func (e *EthModuleDummy) EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) { - return 0, ErrImplementMe + return 0, ErrModuleDisabled } func (e *EthModuleDummy) EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) { - return ethtypes.EthBigIntZero, ErrImplementMe + return ethtypes.EthBigIntZero, ErrModuleDisabled } func (e *EthModuleDummy) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) { - return 0, ErrImplementMe + return 0, ErrModuleDisabled } func (e *EthModuleDummy) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) { - return nil, ErrImplementMe + return nil, ErrModuleDisabled } func (e *EthModuleDummy) EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) { - return ethtypes.EthBigIntZero, ErrImplementMe + return ethtypes.EthBigIntZero, ErrModuleDisabled } func (e *EthModuleDummy) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) { - return ethtypes.EthHash{}, ErrImplementMe + return ethtypes.EthHash{}, ErrModuleDisabled } + +var _ EthModuleAPI = &EthModuleDummy{} diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 71770e847..7eb992a22 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -21,11 +21,13 @@ import ( builtintypes "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/go-state-types/builtin/v10/eam" "github.com/filecoin-project/go-state-types/builtin/v10/evm" + "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/ethhashlookup" "github.com/filecoin-project/lotus/chain/events/filter" "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/stmgr" @@ -43,6 +45,8 @@ type EthModuleAPI interface { EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) + EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) + EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) @@ -107,11 +111,10 @@ var ( // accepts as the best parent tipset, based on the blocks it is accumulating // within the HEAD tipset. type EthModule struct { - fx.In - - Chain *store.ChainStore - Mpool *messagepool.MessagePool - StateManager *stmgr.StateManager + Chain *store.ChainStore + Mpool *messagepool.MessagePool + StateManager *stmgr.StateManager + EthTxHashManager *EthTxHashManager ChainAPI MpoolAPI @@ -254,10 +257,18 @@ func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash *ethtype return nil, nil } - cid := txHash.ToCid() + c, err := a.EthTxHashManager.TransactionHashLookup.GetCidFromHash(*txHash) + if err != nil { + log.Debug("could not find transaction hash %s in lookup table", txHash.String()) + } + + // This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message + if c == cid.Undef { + c = txHash.ToCid() + } // first, try to get the cid from mined transactions - msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true) + msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, c, api.LookbackNoLimit, true) if err == nil { tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, -1, a.Chain, a.StateAPI) if err == nil { @@ -274,8 +285,8 @@ func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash *ethtype } for _, p := range pending { - if p.Cid() == cid { - tx, err := newEthTxFromFilecoinMessage(ctx, p, a.StateAPI) + if p.Cid() == c { + tx, err := NewEthTxFromFilecoinMessage(ctx, p, a.StateAPI) if err != nil { return nil, fmt.Errorf("could not convert Filecoin message into tx: %s", err) } @@ -286,6 +297,53 @@ func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash *ethtype return nil, nil } +func (a *EthModule) EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) { + // Ethereum's behavior is to return null when the txHash is invalid, so we use nil to check if txHash is valid + if txHash == nil { + return nil, nil + } + + c, err := a.EthTxHashManager.TransactionHashLookup.GetCidFromHash(*txHash) + // We fall out of the first condition and continue + if errors.Is(err, ethhashlookup.ErrNotFound) { + log.Debug("could not find transaction hash %s in lookup table", txHash.String()) + } else if err != nil { + return nil, xerrors.Errorf("database error: %w", err) + } else { + return &c, nil + } + + // This isn't an eth transaction we have the mapping for, so let's try looking it up as a filecoin message + if c == cid.Undef { + c = txHash.ToCid() + } + + _, err = a.StateAPI.Chain.GetSignedMessage(ctx, c) + if err == nil { + // This is an Eth Tx, Secp message, Or BLS message in the mpool + return &c, nil + } + + _, err = a.StateAPI.Chain.GetMessage(ctx, c) + if err == nil { + // This is a BLS message + return &c, nil + } + + // Ethereum clients expect an empty response when the message was not found + return nil, nil +} + +func (a *EthModule) EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) { + hash, err := EthTxHashFromFilecoinMessageCid(ctx, cid, a.StateAPI) + if hash == ethtypes.EmptyEthHash { + // not found + return nil, nil + } + + return &hash, err +} + func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkParam string) (ethtypes.EthUint64, error) { addr, err := sender.ToFilecoinAddress() if err != nil { @@ -305,10 +363,18 @@ func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender ethtypes. } func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*api.EthTxReceipt, error) { - cid := txHash.ToCid() - - msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true) + c, err := a.EthTxHashManager.TransactionHashLookup.GetCidFromHash(txHash) if err != nil { + log.Debug("could not find transaction hash %s in lookup table", txHash.String()) + } + + // This isn't an eth transaction we have the mapping for, so let's look it up as a filecoin message + if c == cid.Undef { + c = txHash.ToCid() + } + + msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, c, api.LookbackNoLimit, true) + if err != nil || msgLookup == nil { return nil, nil } @@ -317,7 +383,7 @@ func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash ethtype return nil, nil } - replay, err := a.StateAPI.StateReplay(ctx, types.EmptyTSK, cid) + replay, err := a.StateAPI.StateReplay(ctx, types.EmptyTSK, c) if err != nil { return nil, nil } @@ -640,11 +706,12 @@ func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.Et smsg.Message.Method = builtinactors.MethodSend } - cid, err := a.MpoolAPI.MpoolPush(ctx, smsg) + _, err = a.MpoolAPI.MpoolPush(ctx, smsg) if err != nil { return ethtypes.EmptyEthHash, err } - return ethtypes.EthHashFromCid(cid) + + return ethtypes.EthHashFromTxBytes(rawTx), nil } func (a *EthModule) ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.EthCall) (*types.Message, error) { @@ -791,7 +858,7 @@ func (e *EthEvent) EthGetLogs(ctx context.Context, filterSpec *ethtypes.EthFilte _ = e.uninstallFilter(ctx, f) - return ethFilterResultFromEvents(ces) + return ethFilterResultFromEvents(ces, e.SubManager.StateAPI) } func (e *EthEvent) EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { @@ -806,11 +873,11 @@ func (e *EthEvent) EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilte switch fc := f.(type) { case filterEventCollector: - return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx)) + return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx), e.SubManager.StateAPI) case filterTipSetCollector: return ethFilterResultFromTipSets(fc.TakeCollectedTipSets(ctx)) case filterMessageCollector: - return ethFilterResultFromMessages(fc.TakeCollectedMessages(ctx)) + return ethFilterResultFromMessages(fc.TakeCollectedMessages(ctx), e.SubManager.StateAPI) } return nil, xerrors.Errorf("unknown filter type") @@ -828,7 +895,7 @@ func (e *EthEvent) EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID switch fc := f.(type) { case filterEventCollector: - return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx)) + return ethFilterResultFromEvents(fc.TakeCollectedEvents(ctx), e.SubManager.StateAPI) } return nil, xerrors.Errorf("wrong filter type") @@ -885,18 +952,20 @@ func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtype // Here the client is looking for events between the head and some future height ts := e.Chain.GetHeaviestTipSet() if maxHeight-ts.Height() > e.MaxFilterHeightRange { - return nil, xerrors.Errorf("invalid epoch range") + return nil, xerrors.Errorf("invalid epoch range: to block is too far in the future (maximum: %d)", e.MaxFilterHeightRange) } } else if minHeight >= 0 && maxHeight == -1 { // Here the client is looking for events between some time in the past and the current head ts := e.Chain.GetHeaviestTipSet() if ts.Height()-minHeight > e.MaxFilterHeightRange { - return nil, xerrors.Errorf("invalid epoch range") + return nil, xerrors.Errorf("invalid epoch range: from block is too far in the past (maximum: %d)", e.MaxFilterHeightRange) } } else if minHeight >= 0 && maxHeight >= 0 { - if minHeight > maxHeight || maxHeight-minHeight > e.MaxFilterHeightRange { - return nil, xerrors.Errorf("invalid epoch range") + if minHeight > maxHeight { + return nil, xerrors.Errorf("invalid epoch range: to block (%d) must be after from block (%d)", minHeight, maxHeight) + } else if maxHeight-minHeight > e.MaxFilterHeightRange { + return nil, xerrors.Errorf("invalid epoch range: range between to and from blocks is too large (maximum: %d)", e.MaxFilterHeightRange) } } @@ -912,13 +981,16 @@ func (e *EthEvent) installEthFilterSpec(ctx context.Context, filterSpec *ethtype } for idx, vals := range filterSpec.Topics { + if len(vals) == 0 { + continue + } // Ethereum topics are emitted using `LOG{0..4}` opcodes resulting in topics1..4 key := fmt.Sprintf("topic%d", idx+1) - keyvals := make([][]byte, len(vals)) - for i, v := range vals { - keyvals[i] = v[:] + for _, v := range vals { + buf := make([]byte, len(v[:])) + copy(buf, v[:]) + keys[key] = append(keys[key], buf) } - keys[key] = keyvals } return e.EventFilterManager.Install(ctx, minHeight, maxHeight, tipsetCid, addresses, keys) @@ -1141,14 +1213,14 @@ type filterEventCollector interface { } type filterMessageCollector interface { - TakeCollectedMessages(context.Context) []cid.Cid + TakeCollectedMessages(context.Context) []*types.SignedMessage } type filterTipSetCollector interface { TakeCollectedTipSets(context.Context) []types.TipSetKey } -func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*ethtypes.EthFilterResult, error) { +func ethFilterResultFromEvents(evs []*filter.CollectedEvent, sa StateAPI) (*ethtypes.EthFilterResult, error) { res := ðtypes.EthFilterResult{} for _, ev := range evs { log := ethtypes.EthLog{ @@ -1161,7 +1233,7 @@ func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*ethtypes.EthFilte var err error for _, entry := range ev.Entries { - value := ethtypes.EthBytes(leftpad32(decodeLogBytes(entry.Value))) + value := ethtypes.EthBytes(leftpad32(entry.Value)) // value has already been cbor-decoded but see https://github.com/filecoin-project/ref-fvm/issues/1345 if entry.Key == ethtypes.EthTopic1 || entry.Key == ethtypes.EthTopic2 || entry.Key == ethtypes.EthTopic3 || entry.Key == ethtypes.EthTopic4 { log.Topics = append(log.Topics, value) } else { @@ -1174,11 +1246,10 @@ func ethFilterResultFromEvents(evs []*filter.CollectedEvent) (*ethtypes.EthFilte return nil, err } - log.TransactionHash, err = ethtypes.EthHashFromCid(ev.MsgCid) + log.TransactionHash, err = EthTxHashFromFilecoinMessageCid(context.TODO(), ev.MsgCid, sa) if err != nil { return nil, err } - c, err := ev.TipSetKey.Cid() if err != nil { return nil, err @@ -1213,11 +1284,11 @@ func ethFilterResultFromTipSets(tsks []types.TipSetKey) (*ethtypes.EthFilterResu return res, nil } -func ethFilterResultFromMessages(cs []cid.Cid) (*ethtypes.EthFilterResult, error) { +func ethFilterResultFromMessages(cs []*types.SignedMessage, sa StateAPI) (*ethtypes.EthFilterResult, error) { res := ðtypes.EthFilterResult{} for _, c := range cs { - hash, err := ethtypes.EthHashFromCid(c) + hash, err := EthTxHashFromSignedFilecoinMessage(context.TODO(), c, sa) if err != nil { return nil, err } @@ -1316,7 +1387,7 @@ func (e *ethSubscription) start(ctx context.Context) { var err error switch vt := v.(type) { case *filter.CollectedEvent: - resp.Result, err = ethFilterResultFromEvents([]*filter.CollectedEvent{vt}) + resp.Result, err = ethFilterResultFromEvents([]*filter.CollectedEvent{vt}, e.StateAPI) case *types.TipSet: eb, err := newEthBlockFromFilecoinTipSet(ctx, vt, true, e.Chain, e.ChainAPI, e.StateAPI) if err != nil { @@ -1391,18 +1462,15 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx } gasUsed += msgLookup.Receipt.GasUsed + tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, txIdx, cs, sa) + if err != nil { + return ethtypes.EthBlock{}, nil + } + if fullTxInfo { - tx, err := newEthTxFromFilecoinMessageLookup(ctx, msgLookup, txIdx, cs, sa) - if err != nil { - return ethtypes.EthBlock{}, nil - } block.Transactions = append(block.Transactions, tx) } else { - hash, err := ethtypes.EthHashFromCid(msg.Cid()) - if err != nil { - return ethtypes.EthBlock{}, err - } - block.Transactions = append(block.Transactions, hash.String()) + block.Transactions = append(block.Transactions, tx.Hash.String()) } } @@ -1454,19 +1522,42 @@ func lookupEthAddress(ctx context.Context, addr address.Address, sa StateAPI) (e return ethtypes.EthAddressFromFilecoinAddress(idAddr) } -func newEthTxFromFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthTx, error) { - fromEthAddr, err := lookupEthAddress(ctx, smsg.Message.From, sa) - if err != nil { - return ethtypes.EthTx{}, err +func EthTxHashFromFilecoinMessageCid(ctx context.Context, c cid.Cid, sa StateAPI) (ethtypes.EthHash, error) { + smsg, err := sa.Chain.GetSignedMessage(ctx, c) + if err == nil { + // This is an Eth Tx, Secp message, Or BLS message in the mpool + return EthTxHashFromSignedFilecoinMessage(ctx, smsg, sa) } - toEthAddr, err := lookupEthAddress(ctx, smsg.Message.To, sa) - if err != nil { - return ethtypes.EthTx{}, err + _, err = sa.Chain.GetMessage(ctx, c) + if err == nil { + // This is a BLS message + return ethtypes.EthHashFromCid(c) } + return ethtypes.EmptyEthHash, nil +} + +func EthTxHashFromSignedFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthHash, error) { + if smsg.Signature.Type == crypto.SigTypeDelegated { + ethTx, err := NewEthTxFromFilecoinMessage(ctx, smsg, sa) + if err != nil { + return ethtypes.EmptyEthHash, err + } + return ethTx.Hash, nil + } + + return ethtypes.EthHashFromCid(smsg.Cid()) +} + +func NewEthTxFromFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthTx, error) { + // Ignore errors here so we can still parse non-eth messages + fromEthAddr, _ := lookupEthAddress(ctx, smsg.Message.From, sa) + toEthAddr, _ := lookupEthAddress(ctx, smsg.Message.To, sa) + toAddr := &toEthAddr input := smsg.Message.Params + var err error // Check to see if we need to decode as contract deployment. // We don't need to resolve the to address, because there's only one form (an ID). if smsg.Message.To == builtintypes.EthereumAddressManagerActorAddr { @@ -1505,26 +1596,38 @@ func newEthTxFromFilecoinMessage(ctx context.Context, smsg *types.SignedMessage, r, s, v = ethtypes.EthBigIntZero, ethtypes.EthBigIntZero, ethtypes.EthBigIntZero } - hash, err := ethtypes.EthHashFromCid(smsg.Cid()) - if err != nil { - return ethtypes.EthTx{}, err - } - tx := ethtypes.EthTx{ - Hash: hash, Nonce: ethtypes.EthUint64(smsg.Message.Nonce), ChainID: ethtypes.EthUint64(build.Eip155ChainId), From: fromEthAddr, To: toAddr, Value: ethtypes.EthBigInt(smsg.Message.Value), Type: ethtypes.EthUint64(2), + Input: input, Gas: ethtypes.EthUint64(smsg.Message.GasLimit), MaxFeePerGas: ethtypes.EthBigInt(smsg.Message.GasFeeCap), MaxPriorityFeePerGas: ethtypes.EthBigInt(smsg.Message.GasPremium), V: v, R: r, S: s, - Input: input, + } + + // This is an eth tx + if smsg.Signature.Type == crypto.SigTypeDelegated { + tx.Hash, err = tx.TxHash() + if err != nil { + return tx, err + } + } else if smsg.Signature.Type == crypto.SigTypeUnknown { // BLS Filecoin message + tx.Hash, err = ethtypes.EthHashFromCid(smsg.Message.Cid()) + if err != nil { + return tx, err + } + } else { // Secp Filecoin Message + tx.Hash, err = ethtypes.EthHashFromCid(smsg.Cid()) + if err != nil { + return tx, err + } } return tx, nil @@ -1537,11 +1640,6 @@ func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLo if msgLookup == nil { return ethtypes.EthTx{}, fmt.Errorf("msg does not exist") } - cid := msgLookup.Message - txHash, err := ethtypes.EthHashFromCid(cid) - if err != nil { - return ethtypes.EthTx{}, err - } ts, err := cs.LoadTipSet(ctx, msgLookup.TipSet) if err != nil { @@ -1583,10 +1681,21 @@ func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLo smsg, err := cs.GetSignedMessage(ctx, msgLookup.Message) if err != nil { - return ethtypes.EthTx{}, err + // We couldn't find the signed message, it might be a BLS message, so search for a regular message. + msg, err := cs.GetMessage(ctx, msgLookup.Message) + if err != nil { + return ethtypes.EthTx{}, err + } + smsg = &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeUnknown, + Data: nil, + }, + } } - tx, err := newEthTxFromFilecoinMessage(ctx, smsg, sa) + tx, err := NewEthTxFromFilecoinMessage(ctx, smsg, sa) if err != nil { return ethtypes.EthTx{}, err } @@ -1597,7 +1706,6 @@ func newEthTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLo ) tx.ChainID = ethtypes.EthUint64(build.Eip155ChainId) - tx.Hash = txHash tx.BlockHash = &blkHash tx.BlockNumber = &bn tx.TransactionIndex = &ti @@ -1668,7 +1776,7 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook } for _, entry := range evt.Entries { - value := ethtypes.EthBytes(leftpad32(decodeLogBytes(entry.Value))) + value := ethtypes.EthBytes(leftpad32(entry.Value)) // value has already been cbor-decoded but see https://github.com/filecoin-project/ref-fvm/issues/1345 if entry.Key == ethtypes.EthTopic1 || entry.Key == ethtypes.EthTopic2 || entry.Key == ethtypes.EthTopic3 || entry.Key == ethtypes.EthTopic4 { l.Topics = append(l.Topics, value) } else { @@ -1701,19 +1809,82 @@ func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLook return receipt, nil } -// decodeLogBytes decodes a CBOR-serialized array into its original form. -// -// This function swallows errors and returns the original array if it failed -// to decode. -func decodeLogBytes(orig []byte) []byte { - if orig == nil { - return orig +func (m *EthTxHashManager) Apply(ctx context.Context, from, to *types.TipSet) error { + for _, blk := range to.Blocks() { + _, smsgs, err := m.StateAPI.Chain.MessagesForBlock(ctx, blk) + if err != nil { + return err + } + + for _, smsg := range smsgs { + if smsg.Signature.Type != crypto.SigTypeDelegated { + continue + } + + hash, err := EthTxHashFromSignedFilecoinMessage(ctx, smsg, m.StateAPI) + if err != nil { + return err + } + + err = m.TransactionHashLookup.UpsertHash(hash, smsg.Cid()) + if err != nil { + return err + } + } } - decoded, err := cbg.ReadByteArray(bytes.NewReader(orig), uint64(len(orig))) - if err != nil { - return orig + + return nil +} + +type EthTxHashManager struct { + StateAPI StateAPI + TransactionHashLookup *ethhashlookup.EthTxHashLookup +} + +func (m *EthTxHashManager) Revert(ctx context.Context, from, to *types.TipSet) error { + return nil +} + +func WaitForMpoolUpdates(ctx context.Context, ch <-chan api.MpoolUpdate, manager *EthTxHashManager) { + for { + select { + case <-ctx.Done(): + return + case u := <-ch: + if u.Type != api.MpoolAdd { + continue + } + if u.Message.Signature.Type != crypto.SigTypeDelegated { + continue + } + + ethTx, err := NewEthTxFromFilecoinMessage(ctx, u.Message, manager.StateAPI) + if err != nil { + log.Errorf("error converting filecoin message to eth tx: %s", err) + } + + err = manager.TransactionHashLookup.UpsertHash(ethTx.Hash, u.Message.Cid()) + if err != nil { + log.Errorf("error inserting tx mapping to db: %s", err) + } + } + } +} + +func EthTxHashGC(ctx context.Context, retentionDays int, manager *EthTxHashManager) { + if retentionDays == 0 { + return + } + + gcPeriod := 1 * time.Hour + for { + entriesDeleted, err := manager.TransactionHashLookup.DeleteEntriesOlderThan(retentionDays) + if err != nil { + log.Errorf("error garbage collecting eth transaction hash database: %s", err) + } + log.Info("garbage collection run on eth transaction hash lookup database. %d entries deleted", entriesDeleted) + time.Sleep(gcPeriod) } - return decoded } // TODO we could also emit full EVM words from the EVM runtime, but not doing so diff --git a/node/impl/full/wallet.go b/node/impl/full/wallet.go index 67bb96101..0927e5aca 100644 --- a/node/impl/full/wallet.go +++ b/node/impl/full/wallet.go @@ -11,9 +11,11 @@ import ( "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/messagesigner" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/lotus/chain/wallet/key" "github.com/filecoin-project/lotus/lib/sigs" ) @@ -51,12 +53,20 @@ func (a *WalletAPI) WalletSignMessage(ctx context.Context, k address.Address, ms return nil, xerrors.Errorf("failed to resolve ID address: %w", keyAddr) } + keyInfo, err := a.Wallet.WalletExport(ctx, k) + if err != nil { + return nil, err + } + sb, err := messagesigner.SigningBytes(msg, key.ActSigType(keyInfo.Type)) + if err != nil { + return nil, err + } mb, err := msg.ToStorageBlock() if err != nil { return nil, xerrors.Errorf("serializing message: %w", err) } - sig, err := a.Wallet.WalletSign(ctx, keyAddr, mb.Cid().Bytes(), api.MsgMeta{ + sig, err := a.Wallet.WalletSign(ctx, keyAddr, sb, api.MsgMeta{ Type: api.MTChainMsg, Extra: mb.RawData(), }) diff --git a/node/modules/actorevent.go b/node/modules/actorevent.go index 1c574cb68..eb5afb8e6 100644 --- a/node/modules/actorevent.go +++ b/node/modules/actorevent.go @@ -2,6 +2,7 @@ package modules import ( "context" + "path/filepath" "time" "github.com/multiformats/go-varint" @@ -20,6 +21,7 @@ import ( "github.com/filecoin-project/lotus/node/config" "github.com/filecoin-project/lotus/node/impl/full" "github.com/filecoin-project/lotus/node/modules/helpers" + "github.com/filecoin-project/lotus/node/repo" ) type EventAPI struct { @@ -31,16 +33,16 @@ type EventAPI struct { var _ events.EventAPI = &EventAPI{} -func EthEventAPI(cfg config.ActorEventConfig) func(helpers.MetricsCtx, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.EthEvent, error) { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.EthEvent, error) { +func EthEventAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI) (*full.EthEvent, error) { + return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI) (*full.EthEvent, error) { ctx := helpers.LifecycleCtx(mctx, lc) ee := &full.EthEvent{ Chain: cs, - MaxFilterHeightRange: abi.ChainEpoch(cfg.MaxFilterHeightRange), + MaxFilterHeightRange: abi.ChainEpoch(cfg.Events.MaxFilterHeightRange), } - if !cfg.EnableRealTimeFilterAPI { + if !cfg.EnableEthRPC || cfg.Events.DisableRealTimeFilterAPI { // all event functionality is disabled // the historic filter API relies on the real time one return ee, nil @@ -51,21 +53,32 @@ func EthEventAPI(cfg config.ActorEventConfig) func(helpers.MetricsCtx, fx.Lifecy StateAPI: stateapi, ChainAPI: chainapi, } - ee.FilterStore = filter.NewMemFilterStore(cfg.MaxFilters) + ee.FilterStore = filter.NewMemFilterStore(cfg.Events.MaxFilters) // Start garbage collection for filters lc.Append(fx.Hook{ OnStart: func(context.Context) error { - go ee.GC(ctx, time.Duration(cfg.FilterTTL)) + go ee.GC(ctx, time.Duration(cfg.Events.FilterTTL)) return nil }, }) // Enable indexing of actor events var eventIndex *filter.EventIndex - if cfg.EnableHistoricFilterAPI { + if !cfg.Events.DisableHistoricFilterAPI { + var dbPath string + if cfg.Events.DatabasePath == "" { + sqlitePath, err := r.SqlitePath() + if err != nil { + return nil, err + } + dbPath = filepath.Join(sqlitePath, "events.db") + } else { + dbPath = cfg.Events.DatabasePath + } + var err error - eventIndex, err = filter.NewEventIndex(cfg.ActorEventDatabasePath) + eventIndex, err = filter.NewEventIndex(dbPath) if err != nil { return nil, err } @@ -103,13 +116,13 @@ func EthEventAPI(cfg config.ActorEventConfig) func(helpers.MetricsCtx, fx.Lifecy return *actor.Address, true }, - MaxFilterResults: cfg.MaxFilterResults, + MaxFilterResults: cfg.Events.MaxFilterResults, } ee.TipSetFilterManager = &filter.TipSetFilterManager{ - MaxFilterResults: cfg.MaxFilterResults, + MaxFilterResults: cfg.Events.MaxFilterResults, } ee.MemPoolFilterManager = &filter.MemPoolFilterManager{ - MaxFilterResults: cfg.MaxFilterResults, + MaxFilterResults: cfg.Events.MaxFilterResults, } const ChainHeadConfidence = 1 diff --git a/node/modules/ethmodule.go b/node/modules/ethmodule.go new file mode 100644 index 000000000..9889233f4 --- /dev/null +++ b/node/modules/ethmodule.go @@ -0,0 +1,79 @@ +package modules + +import ( + "context" + "path/filepath" + + "go.uber.org/fx" + + "github.com/filecoin-project/lotus/chain/ethhashlookup" + "github.com/filecoin-project/lotus/chain/events" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/node/impl/full" + "github.com/filecoin-project/lotus/node/modules/helpers" + "github.com/filecoin-project/lotus/node/repo" +) + +func EthModuleAPI(cfg config.FevmConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, *store.ChainStore, *stmgr.StateManager, EventAPI, *messagepool.MessagePool, full.StateAPI, full.ChainAPI, full.MpoolAPI) (*full.EthModule, error) { + return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, cs *store.ChainStore, sm *stmgr.StateManager, evapi EventAPI, mp *messagepool.MessagePool, stateapi full.StateAPI, chainapi full.ChainAPI, mpoolapi full.MpoolAPI) (*full.EthModule, error) { + sqlitePath, err := r.SqlitePath() + if err != nil { + return nil, err + } + + transactionHashLookup, err := ethhashlookup.NewTransactionHashLookup(filepath.Join(sqlitePath, "txhash.db")) + if err != nil { + return nil, err + } + + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + return transactionHashLookup.Close() + }, + }) + + ethTxHashManager := full.EthTxHashManager{ + StateAPI: stateapi, + TransactionHashLookup: transactionHashLookup, + } + + const ChainHeadConfidence = 1 + + ctx := helpers.LifecycleCtx(mctx, lc) + lc.Append(fx.Hook{ + OnStart: func(context.Context) error { + ev, err := events.NewEventsWithConfidence(ctx, &evapi, ChainHeadConfidence) + if err != nil { + return err + } + + // Tipset listener + _ = ev.Observe(ðTxHashManager) + + ch, err := mp.Updates(ctx) + if err != nil { + return err + } + go full.WaitForMpoolUpdates(ctx, ch, ðTxHashManager) + go full.EthTxHashGC(ctx, cfg.EthTxHashMappingLifetimeDays, ðTxHashManager) + + return nil + }, + }) + + return &full.EthModule{ + Chain: cs, + Mpool: mp, + StateManager: sm, + + ChainAPI: chainapi, + MpoolAPI: mpoolapi, + StateAPI: stateapi, + + EthTxHashManager: ðTxHashManager, + }, nil + } +} diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index 68550e389..bd387babf 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -37,6 +37,7 @@ const ( fsDatastore = "datastore" fsLock = "repo.lock" fsKeystore = "keystore" + fsSqlite = "sqlite" ) func NewRepoTypeFromString(t string) RepoType { @@ -411,6 +412,10 @@ type fsLockedRepo struct { ssErr error ssOnce sync.Once + sqlPath string + sqlErr error + sqlOnce sync.Once + storageLk sync.Mutex configLk sync.Mutex } @@ -515,6 +520,21 @@ func (fsr *fsLockedRepo) SplitstorePath() (string, error) { return fsr.ssPath, fsr.ssErr } +func (fsr *fsLockedRepo) SqlitePath() (string, error) { + fsr.sqlOnce.Do(func() { + path := fsr.join(fsSqlite) + + if err := os.MkdirAll(path, 0755); err != nil { + fsr.sqlErr = err + return + } + + fsr.sqlPath = path + }) + + return fsr.sqlPath, fsr.sqlErr +} + // join joins path elements with fsr.path func (fsr *fsLockedRepo) join(paths ...string) string { return filepath.Join(append([]string{fsr.path}, paths...)...) diff --git a/node/repo/interface.go b/node/repo/interface.go index dd0839559..328862b92 100644 --- a/node/repo/interface.go +++ b/node/repo/interface.go @@ -69,6 +69,9 @@ type LockedRepo interface { // SplitstorePath returns the path for the SplitStore SplitstorePath() (string, error) + // SqlitePath returns the path for the Sqlite database + SqlitePath() (string, error) + // Returns config in this repo Config() (interface{}, error) SetConfig(func(interface{})) error diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 61d960872..7817776a9 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -277,6 +277,14 @@ func (lmem *lockedMemRepo) SplitstorePath() (string, error) { return splitstorePath, nil } +func (lmem *lockedMemRepo) SqlitePath() (string, error) { + sqlitePath := filepath.Join(lmem.Path(), "sqlite") + if err := os.MkdirAll(sqlitePath, 0755); err != nil { + return "", err + } + return sqlitePath, nil +} + func (lmem *lockedMemRepo) ListDatastores(ns string) ([]int64, error) { return nil, nil } diff --git a/scripts/build-appimage-bundle.sh b/scripts/build-appimage-bundle.sh deleted file mode 100755 index d4ce6de77..000000000 --- a/scripts/build-appimage-bundle.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -set -ex - -REQUIRED=( - "ipfs" - "sha512sum" -) -for REQUIRE in "${REQUIRED[@]}" -do - command -v "${REQUIRE}" >/dev/null 2>&1 || echo >&2 "'${REQUIRE}' must be installed" -done - -mkdir bundle -pushd bundle - -export IPFS_PATH=`mktemp -d` -ipfs init -ipfs daemon & -PID="$!" -trap "kill -9 ${PID}" EXIT -sleep 30 - -cp "/tmp/workspace/appimage/Lotus-${CIRCLE_TAG}-x86_64.AppImage" . -sha512sum "Lotus-${CIRCLE_TAG}-x86_64.AppImage" > "Lotus-${CIRCLE_TAG}-x86_64.AppImage.sha512" -ipfs add -q "Lotus-${CIRCLE_TAG}-x86_64.AppImage" > "Lotus-${CIRCLE_TAG}-x86_64.AppImage.cid" -popd diff --git a/scripts/build-arch-bundle.sh b/scripts/build-arch-bundle.sh deleted file mode 100755 index 27b4218f5..000000000 --- a/scripts/build-arch-bundle.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -set -ex - -ARCH=$1 - -REQUIRED=( - "ipfs" - "sha512sum" -) -for REQUIRE in "${REQUIRED[@]}" -do - command -v "${REQUIRE}" >/dev/null 2>&1 || echo >&2 "'${REQUIRE}' must be installed" -done - -mkdir bundle -pushd bundle - -BINARIES=( - "lotus" - "lotus-miner" - "lotus-worker" -) - -export IPFS_PATH=`mktemp -d` -ipfs init -ipfs daemon & -PID="$!" -trap "kill -9 ${PID}" EXIT -sleep 30 - -mkdir -p "${ARCH}/lotus" -pushd "${ARCH}" -for BINARY in "${BINARIES[@]}" -do - cp "../../${ARCH}/${BINARY}" "lotus/" - chmod +x "lotus/${BINARY}" -done - -tar -zcvf "../lotus_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz" lotus -popd -rm -rf "${ARCH}" - -sha512sum "lotus_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz" > "lotus_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz.sha512" - -ipfs add -q "lotus_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz" > "lotus_${CIRCLE_TAG}_${ARCH}-amd64.tar.gz.cid" -popd diff --git a/scripts/publish-arch-release.sh b/scripts/publish-arch-release.sh deleted file mode 100755 index b47ad53fe..000000000 --- a/scripts/publish-arch-release.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env bash -set -e - -ARCH=$1 - -pushd bundle - -# make sure we have a token set, api requests won't work otherwise -if [ -z "${GITHUB_TOKEN}" ]; then - echo "\${GITHUB_TOKEN} not set, publish failed" - exit 1 -fi - -REQUIRED=( - "jq" - "curl" -) -for REQUIRE in "${REQUIRED[@]}" -do - command -v "${REQUIRE}" >/dev/null 2>&1 || echo >&2 "'${REQUIRE}' must be installed" -done - -#see if the release already exists by tag -RELEASE_RESPONSE=` - curl \ - --header "Authorization: token ${GITHUB_TOKEN}" \ - "https://api.github.com/repos/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/releases/tags/${CIRCLE_TAG}" -` -RELEASE_ID=`echo "${RELEASE_RESPONSE}" | jq '.id'` - -if [ "${RELEASE_ID}" = "null" ]; then - echo "creating release" - - COND_CREATE_DISCUSSION="" - PRERELEASE=true - if [[ ${CIRCLE_TAG} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - COND_CREATE_DISCUSSION="\"discussion_category_name\": \"announcement\"," - PRERELEASE=false - fi - - RELEASE_DATA="{ - \"tag_name\": \"${CIRCLE_TAG}\", - \"target_commitish\": \"${CIRCLE_SHA1}\", - ${COND_CREATE_DISCUSSION} - \"name\": \"${CIRCLE_TAG}\", - \"body\": \"\", - \"prerelease\": ${PRERELEASE} - }" - - # create it if it doesn't exist yet - RELEASE_RESPONSE=` - curl \ - --request POST \ - --header "Authorization: token ${GITHUB_TOKEN}" \ - --header "Content-Type: application/json" \ - --data "${RELEASE_DATA}" \ - "https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/${CIRCLE_PROJECT_REPONAME}/releases" - ` -else - echo "release already exists" -fi - -RELEASE_UPLOAD_URL=`echo "${RELEASE_RESPONSE}" | jq -r '.upload_url' | cut -d'{' -f1` -echo "Preparing to send artifacts to ${RELEASE_UPLOAD_URL}" - -if [ $ARCH = 'linux' ]; then -artifacts=( - "lotus_${CIRCLE_TAG}_linux-amd64.tar.gz" - "lotus_${CIRCLE_TAG}_linux-amd64.tar.gz.cid" - "lotus_${CIRCLE_TAG}_linux-amd64.tar.gz.sha512" -) -elif [ $ARCH = 'darwin' ]; then -artifacts=( - "lotus_${CIRCLE_TAG}_darwin-amd64.tar.gz" - "lotus_${CIRCLE_TAG}_darwin-amd64.tar.gz.cid" - "lotus_${CIRCLE_TAG}_darwin-amd64.tar.gz.sha512" -) -elif [ $ARCH = 'appimage' ]; then -artifacts=( - "Lotus-${CIRCLE_TAG}-x86_64.AppImage" - "Lotus-${CIRCLE_TAG}-x86_64.AppImage.cid" - "Lotus-${CIRCLE_TAG}-x86_64.AppImage.sha512" -) -else - echo "$1 is not a supported architecture to publish a release for" 1>&2 - exit 1 -fi - -for RELEASE_FILE in "${artifacts[@]}" -do - echo "Uploading ${RELEASE_FILE}..." - curl \ - --request POST \ - --fail \ - --header "Authorization: token ${GITHUB_TOKEN}" \ - --header "Content-Type: application/octet-stream" \ - --data-binary "@${RELEASE_FILE}" \ - "$RELEASE_UPLOAD_URL?name=$(basename "${RELEASE_FILE}")" - - echo "Uploaded ${RELEASE_FILE}" -done - -popd - -miscellaneous=( - "README.md" - "LICENSE-MIT" - "LICENSE-APACHE" -) -for MISC in "${miscellaneous[@]}" -do - echo "Uploading release bundle: ${MISC}" - curl \ - --request POST \ - --header "Authorization: token ${GITHUB_TOKEN}" \ - --header "Content-Type: application/octet-stream" \ - --data-binary "@${MISC}" \ - "$RELEASE_UPLOAD_URL?name=$(basename "${MISC}")" - - echo "Release bundle uploaded: ${MISC}" -done