From 5518e64574ee2cd8d14cbe04646fecf553b3399b Mon Sep 17 00:00:00 2001 From: Aayush Date: Thu, 19 Jan 2023 11:30:37 -0500 Subject: [PATCH 1/2] fix: chain: put tipsetkey upon expansion of tipset --- chain/store/store.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/chain/store/store.go b/chain/store/store.go index 5ed037ee5..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) From 1e845c61a2cf42c3389be845834a2b91e7fd6089 Mon Sep 17 00:00:00 2001 From: raulk Date: Thu, 19 Jan 2023 18:25:23 +0000 Subject: [PATCH 2/2] add integration test to catch tipset CID flakiness. (#10071) --- .circleci/config.yml | 5 +++ itests/deals_padding_test.go | 2 +- itests/deals_power_test.go | 2 +- itests/eth_block_hash_test.go | 65 +++++++++++++++++++++++++++++++++++ itests/eth_deploy_test.go | 1 + itests/kit/node_full.go | 18 +++++++--- 6 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 itests/eth_block_hash_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index db625b2be..9315461f8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -730,6 +730,11 @@ workflows: 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 suite: itest-eth_deploy 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 f73076d02..0d7ab1829 100644 --- a/itests/eth_deploy_test.go +++ b/itests/eth_deploy_test.go @@ -100,6 +100,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/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