From 6058d3c41a8571b6ab3f56cfdcbab8bb40523c36 Mon Sep 17 00:00:00 2001 From: Arnau B Date: Mon, 28 Dec 2020 12:17:00 +0100 Subject: [PATCH] Fix next forgers api response --- api/api_test.go | 149 +++++++++++++++++++++++++++++++------- api/bids_test.go | 2 +- api/slots_test.go | 16 ++-- api/state.go | 48 +++++++++++- api/state_test.go | 55 ++++++-------- api/swagger.yml | 3 + common/ethauction.go | 2 +- db/historydb/historydb.go | 17 +++++ db/historydb/views.go | 6 ++ test/historydb.go | 2 +- 10 files changed, 226 insertions(+), 74 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index a9e2bb2..4db59a3 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -177,6 +177,7 @@ type testCommon struct { auctionVars common.AuctionVariables rollupVars common.RollupVariables wdelayerVars common.WDelayerVariables + nextForgers []NextForger } var tc testCommon @@ -219,7 +220,6 @@ func TestMain(m *testing.M) { // L2DB l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour) test.WipeDB(l2DB.DB()) // this will clean HistoryDB and L2DB - // Config (smart contract constants) _config := getConfigTest(chainID) config = configAPI{ @@ -357,26 +357,128 @@ func TestMain(m *testing.M) { panic(err) } - // Generate Bids and add them to HistoryDB - const nBids = 20 - commonBids := test.GenBids(nBids, commonBlocks, commonCoords) - if err = api.h.AddBids(commonBids); err != nil { + // Test next forgers + // Set auction vars + // Slots 3 and 6 will have bids that will be invalidated because of minBid update + // Slots 4 and 7 will have valid bids, the rest will be cordinator slots + var slot3MinBid int64 = 3 + var slot4MinBid int64 = 4 + var slot6MinBid int64 = 6 + var slot7MinBid int64 = 7 + // First update will indicate how things behave from slot 0 + var defaultSlotSetBid [6]*big.Int = [6]*big.Int{ + big.NewInt(10), // Slot 0 min bid + big.NewInt(10), // Slot 1 min bid + big.NewInt(10), // Slot 2 min bid + big.NewInt(slot3MinBid), // Slot 3 min bid + big.NewInt(slot4MinBid), // Slot 4 min bid + big.NewInt(10), // Slot 5 min bid + } + auctionVars := common.AuctionVariables{ + EthBlockNum: int64(2), + DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"), + DefaultSlotSetBid: defaultSlotSetBid, + DefaultSlotSetBidSlotNum: 0, + Outbidding: uint16(1), + SlotDeadline: uint8(20), + BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"), + BootCoordinatorURL: "https://boot.coordinator.io", + ClosedAuctionSlots: uint16(10), + OpenAuctionSlots: uint16(20), + } + if err := api.h.AddAuctionVars(&auctionVars); err != nil { + panic(err) + } + // Last update in auction vars will indicate how things will behave from slot 5 + defaultSlotSetBid = [6]*big.Int{ + big.NewInt(10), // Slot 5 min bid + big.NewInt(slot6MinBid), // Slot 6 min bid + big.NewInt(slot7MinBid), // Slot 7 min bid + big.NewInt(10), // Slot 8 min bid + big.NewInt(10), // Slot 9 min bid + big.NewInt(10), // Slot 10 min bid + } + auctionVars = common.AuctionVariables{ + EthBlockNum: int64(3), + DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"), + DefaultSlotSetBid: defaultSlotSetBid, + DefaultSlotSetBidSlotNum: 5, + Outbidding: uint16(1), + SlotDeadline: uint8(20), + BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"), + BootCoordinatorURL: "https://boot.coordinator.io", + ClosedAuctionSlots: uint16(10), + OpenAuctionSlots: uint16(20), + } + if err := api.h.AddAuctionVars(&auctionVars); err != nil { panic(err) } - // Generate SC vars and add them to HistoryDB (if needed) - var defaultSlotSetBid [6]*big.Int = [6]*big.Int{big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10)} - auctionVars := common.AuctionVariables{ - EthBlockNum: int64(2), - DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"), - DefaultSlotSetBid: defaultSlotSetBid, - Outbidding: uint16(1), - SlotDeadline: uint8(20), - BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"), - BootCoordinatorURL: "https://boot.coordinator.io", - ClosedAuctionSlots: uint16(2), - OpenAuctionSlots: uint16(5), + // Generate Bids and add them to HistoryDB + bids := []common.Bid{} + // Slot 1 and 2, no bids, wins boot coordinator + // Slot 3, below what's going to be the minimum (wins boot coordinator) + bids = append(bids, common.Bid{ + SlotNum: 3, + BidValue: big.NewInt(slot3MinBid - 1), + EthBlockNum: commonBlocks[0].Num, + Bidder: commonCoords[0].Bidder, + }) + // Slot 4, valid bid (wins bidder) + bids = append(bids, common.Bid{ + SlotNum: 4, + BidValue: big.NewInt(slot4MinBid), + EthBlockNum: commonBlocks[0].Num, + Bidder: commonCoords[0].Bidder, + }) + // Slot 5 no bids, wins boot coordinator + // Slot 6, below what's going to be the minimum (wins boot coordinator) + bids = append(bids, common.Bid{ + SlotNum: 6, + BidValue: big.NewInt(slot6MinBid - 1), + EthBlockNum: commonBlocks[0].Num, + Bidder: commonCoords[0].Bidder, + }) + // Slot 7, valid bid (wins bidder) + bids = append(bids, common.Bid{ + SlotNum: 7, + BidValue: big.NewInt(slot7MinBid), + EthBlockNum: commonBlocks[0].Num, + Bidder: commonCoords[0].Bidder, + }) + if err = api.h.AddBids(bids); err != nil { + panic(err) + } + bootForger := NextForger{ + Coordinator: historydb.CoordinatorAPI{ + Bidder: ethCommon.HexToAddress("0x0111111111111111111111111111111111111111"), + Forger: ethCommon.HexToAddress("0x0111111111111111111111111111111111111111"), + URL: "https://bootCoordinator", + }, + } + // Set next forgers: set all as boot coordinator then replace the non boot coordinators + nextForgers := []NextForger{} + var initBlock int64 = 140 + var deltaBlocks int64 = 40 + for i := 1; i < int(auctionVars.ClosedAuctionSlots)+2; i++ { + fromBlock := initBlock + deltaBlocks*int64(i-1) + bootForger.Period = Period{ + SlotNum: int64(i), + FromBlock: fromBlock, + ToBlock: fromBlock + deltaBlocks - 1, + } + nextForgers = append(nextForgers, bootForger) + } + // Set next forgers that aren't the boot coordinator + nonBootForger := historydb.CoordinatorAPI{ + Bidder: commonCoords[0].Bidder, + Forger: commonCoords[0].Forger, + URL: commonCoords[0].URL, } + // Slot 4 + nextForgers[3].Coordinator = nonBootForger + // Slot 7 + nextForgers[6].Coordinator = nonBootForger var buckets [common.RollupConstNumBuckets]common.BucketParams for i := range buckets { @@ -386,6 +488,7 @@ func TestMain(m *testing.M) { buckets[i].MaxWithdrawals = big.NewInt(int64(i) * 10000) } + // Generate SC vars and add them to HistoryDB (if needed) rollupVars := common.RollupVariables{ EthBlockNum: int64(3), FeeAddToken: big.NewInt(100), @@ -399,14 +502,9 @@ func TestMain(m *testing.M) { WithdrawalDelay: uint64(3000), } - err = api.h.AddAuctionVars(&auctionVars) - if err != nil { - panic(err) - } - // Generate test data, as expected to be received/sended from/to the API testCoords := genTestCoordinators(commonCoords) - testBids := genTestBids(commonBlocks, testCoords, commonBids) + testBids := genTestBids(commonBlocks, testCoords, bids) testExits := genTestExits(commonExitTree, testTokens, commonAccounts) testTxs := genTestTxs(commonL1Txs, commonL2Txs, commonAccounts, testTokens, commonBlocks) testBatches, testFullBatches := genTestBatches(commonBlocks, commonBatches, testTxs) @@ -434,8 +532,11 @@ func TestMain(m *testing.M) { auctionVars: auctionVars, rollupVars: rollupVars, wdelayerVars: wdelayerVars, + nextForgers: nextForgers, } + // Run tests + result := m.Run() // Fake server if os.Getenv("FAKE_SERVER") == "yes" { for { @@ -443,8 +544,6 @@ func TestMain(m *testing.M) { time.Sleep(30 * time.Second) } } - // Run tests - result := m.Run() // Stop server if err := server.Shutdown(context.Background()); err != nil { panic(err) diff --git a/api/bids_test.go b/api/bids_test.go index ab19522..8081b1f 100644 --- a/api/bids_test.go +++ b/api/bids_test.go @@ -141,7 +141,7 @@ func TestGetBids(t *testing.T) { err = doBadReq("GET", path, nil, 400) assert.NoError(t, err) // 404 - path = fmt.Sprintf("%s?slotNum=%d&bidderAddr=%s", endpoint, tc.bids[0].SlotNum, tc.bids[1].Bidder.String()) + path = fmt.Sprintf("%s?slotNum=%d&bidderAddr=%s", endpoint, 5, tc.bids[1].Bidder.String()) err = doBadReq("GET", path, nil, 404) assert.NoError(t, err) } diff --git a/api/slots_test.go b/api/slots_test.go index 09a07d4..0a47e66 100644 --- a/api/slots_test.go +++ b/api/slots_test.go @@ -62,13 +62,13 @@ func (a *API) genTestSlots(nSlots int, lastBlockNum int64, bids []testBid, aucti return tSlots } -func (a *API) getEmptyTestSlot(slotNum int64) testSlot { - firstBlock, lastBlock := a.getFirstLastBlock(slotNum) +func (a *API) getEmptyTestSlot(slotNum, lastBlock int64, auctionVars common.AuctionVariables) testSlot { + firstSlotBlock, lastSlotBlock := a.getFirstLastBlock(slotNum) slot := testSlot{ SlotNum: slotNum, - FirstBlock: firstBlock, - LastBlock: lastBlock, - OpenAuction: false, + FirstBlock: firstSlotBlock, + LastBlock: lastSlotBlock, + OpenAuction: a.isOpenAuction(lastBlock, slotNum, auctionVars), WinnerBid: nil, } return slot @@ -98,7 +98,7 @@ func TestGetSlot(t *testing.T) { nil, &fetchedSlot, ), ) - emptySlot := api.getEmptyTestSlot(slotNum) + emptySlot := api.getEmptyTestSlot(slotNum, api.status.Network.LastSyncBlock, tc.auctionVars) assertSlot(t, emptySlot, fetchedSlot) // Invalid slotNum @@ -127,7 +127,7 @@ func TestGetSlots(t *testing.T) { assert.NoError(t, err) allSlots := tc.slots for i := tc.slots[len(tc.slots)-1].SlotNum; i < maxSlotNum; i++ { - emptySlot := api.getEmptyTestSlot(i + 1) + emptySlot := api.getEmptyTestSlot(i+1, api.status.Network.LastSyncBlock, tc.auctionVars) allSlots = append(allSlots, emptySlot) } assertSlots(t, allSlots, fetchedSlots) @@ -148,7 +148,7 @@ func TestGetSlots(t *testing.T) { // maxSlotNum & wonByEthereumAddress fetchedSlots = []testSlot{} limit = 1 - bidderAddr := tc.coordinators[2].Bidder + bidderAddr := tc.coordinators[0].Bidder path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s&limit=%d", endpoint, maxSlotNum, bidderAddr.String(), limit) err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter) assert.NoError(t, err) diff --git a/api/state.go b/api/state.go index 89b4c5a..af7c1e9 100644 --- a/api/state.go +++ b/api/state.go @@ -2,6 +2,7 @@ package api import ( "database/sql" + "math/big" "net/http" "time" @@ -122,6 +123,21 @@ func (a *API) getNextForgers(lastBlock common.Block, currentSlot, lastClosedSlot return nil, tracerr.Wrap(err) } nextForgers := []NextForger{} + // Get min bid info + var minBidInfo []historydb.MinBidInfo + if currentSlot >= a.status.Auction.DefaultSlotSetBidSlotNum { + // All min bids can be calculated with the last update of AuctionVariables + minBidInfo = []historydb.MinBidInfo{{ + DefaultSlotSetBid: a.status.Auction.DefaultSlotSetBid, + DefaultSlotSetBidSlotNum: a.status.Auction.DefaultSlotSetBidSlotNum, + }} + } else { + // Get all the relevant updates from the DB + minBidInfo, err = a.h.GetAuctionVarsUntilSetSlotNum(lastClosedSlot, int(lastClosedSlot-currentSlot)+1) + if err != nil { + return nil, tracerr.Wrap(err) + } + } // Create nextForger for each slot for i := currentSlot; i <= lastClosedSlot; i++ { fromBlock := i*int64(a.cg.AuctionConstants.BlocksPerSlot) + a.cg.AuctionConstants.GenesisBlockNum @@ -135,11 +151,35 @@ func (a *API) getNextForgers(lastBlock common.Block, currentSlot, lastClosedSlot ToTimestamp: lastBlock.Timestamp.Add(time.Second * time.Duration(secondsPerBlock*(toBlock-lastBlock.Num))), }, } - foundBid := false + foundForger := false // If there is a bid for a slot, get forger (coordinator) for j := range bids { - if bids[j].SlotNum == i { - foundBid = true + slotNum := bids[j].SlotNum + if slotNum == i { + // There's a bid for the slot + // Check if the bid is greater than the minimum required + for i := 0; i < len(minBidInfo); i++ { + // Find the most recent update + if slotNum >= minBidInfo[i].DefaultSlotSetBidSlotNum { + // Get min bid + minBidSelector := slotNum % int64(len(a.status.Auction.DefaultSlotSetBid)) + minBid := minBidInfo[i].DefaultSlotSetBid[minBidSelector] + // Check if the bid has beaten the minimum + bid, ok := new(big.Int).SetString(string(bids[j].BidValue), 10) + if !ok { + return nil, tracerr.New("Wrong bid value, error parsing it as big.Int") + } + if minBid.Cmp(bid) == 1 { + // Min bid is greater than bid, the slot will be forged by boot coordinator + break + } + foundForger = true + break + } + } + if !foundForger { // There is no bid or it's smaller than the minimum + break + } coordinator, err := a.h.GetCoordinatorAPI(bids[j].Bidder) if err != nil { return nil, tracerr.Wrap(err) @@ -149,7 +189,7 @@ func (a *API) getNextForgers(lastBlock common.Block, currentSlot, lastClosedSlot } } // If there is no bid, the coordinator that will forge is boot coordinator - if !foundBid { + if !foundForger { nextForger.Coordinator = bootCoordinator } nextForgers = append(nextForgers, nextForger) diff --git a/api/state_test.go b/api/state_test.go index 267ffdf..7280b5d 100644 --- a/api/state_test.go +++ b/api/state_test.go @@ -2,15 +2,12 @@ package api import ( "testing" - "time" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/stretchr/testify/assert" ) -const secondsPerBlock = 15 - type testStatus struct { Network testNetwork `json:"network"` Metrics historydb.Metrics `json:"metrics"` @@ -49,36 +46,6 @@ func TestSetAuctionVariables(t *testing.T) { assert.Equal(t, tc.auctionVars, api.status.Auction) } -func TestNextForgers(t *testing.T) { - // It's assumed that bids for each slot will be received in increasing order - bestBids := make(map[int64]testBid) - for j := range tc.bids { - bestBids[tc.bids[j].SlotNum] = tc.bids[j] - } - lastBlock := tc.blocks[len(tc.blocks)-1] - for i := int64(0); i < tc.slots[len(tc.slots)-1].SlotNum; i++ { - lastClosedSlot := i + int64(api.status.Auction.ClosedAuctionSlots) - nextForgers, err := api.getNextForgers(tc.blocks[len(tc.blocks)-1], i, lastClosedSlot) - assert.NoError(t, err) - for j := i; j <= lastClosedSlot; j++ { - for q := range nextForgers { - if nextForgers[q].Period.SlotNum == j { - if nextForgers[q].Coordinator.ItemID != 0 { - assert.Equal(t, bestBids[j].Bidder, nextForgers[q].Coordinator.Bidder) - } else { - assert.Equal(t, bootCoordinator.Bidder, nextForgers[q].Coordinator.Bidder) - } - firstBlockSlot, lastBlockSlot := api.getFirstLastBlock(j) - fromTimestamp := lastBlock.Timestamp.Add(time.Second * time.Duration(secondsPerBlock*(firstBlockSlot-lastBlock.Num))) - toTimestamp := lastBlock.Timestamp.Add(time.Second * time.Duration(secondsPerBlock*(lastBlockSlot-lastBlock.Num))) - assert.Equal(t, fromTimestamp.Unix(), nextForgers[q].Period.FromTimestamp.Unix()) - assert.Equal(t, toTimestamp.Unix(), nextForgers[q].Period.ToTimestamp.Unix()) - } - } - } - } -} - func TestUpdateNetworkInfo(t *testing.T) { status := &Network{} assert.Equal(t, status.LastSyncBlock, api.status.Network.LastSyncBlock) @@ -143,23 +110,43 @@ func TestGetState(t *testing.T) { var status testStatus assert.NoError(t, doGoodReq("GET", endpoint, nil, &status)) + + // SC vars assert.Equal(t, tc.rollupVars, status.Rollup) assert.Equal(t, tc.auctionVars, status.Auction) assert.Equal(t, tc.wdelayerVars, status.WithdrawalDelayer) + // Network assert.Equal(t, lastBlock.Num, status.Network.LastEthBlock) assert.Equal(t, lastBlock.Num, status.Network.LastSyncBlock) + // TODO: assert all the batch, not just the batch num assert.Equal(t, lastBatchNum, status.Network.LastBatch.BatchNum) assert.Equal(t, currentSlotNum, status.Network.CurrentSlot) - assert.Equal(t, int(api.status.Auction.ClosedAuctionSlots)+1, len(status.Network.NextForgers)) + assertNextForgers(t, tc.nextForgers, status.Network.NextForgers) + // Metrics + // TODO: perform real asserts (not just greater than 0) assert.Greater(t, status.Metrics.TransactionsPerBatch, float64(0)) assert.Greater(t, status.Metrics.BatchFrequency, float64(0)) assert.Greater(t, status.Metrics.TransactionsPerBatch, float64(0)) assert.Greater(t, status.Metrics.TotalAccounts, int64(0)) assert.Greater(t, status.Metrics.TotalBJJs, int64(0)) assert.Greater(t, status.Metrics.AvgTransactionFee, float64(0)) + // Recommended fee + // TODO: perform real asserts (not just greater than 0) assert.Greater(t, status.RecommendedFee.ExistingAccount, float64(0)) assert.Equal(t, status.RecommendedFee.CreatesAccount, status.RecommendedFee.ExistingAccount*createAccountExtraFeePercentage) assert.Equal(t, status.RecommendedFee.CreatesAccountAndRegister, status.RecommendedFee.ExistingAccount*createAccountInternalExtraFeePercentage) } + +func assertNextForgers(t *testing.T, expected, actual []NextForger) { + assert.Equal(t, len(expected), len(actual)) + for i := range expected { + // ignore timestamps and other metadata + actual[i].Period.FromTimestamp = expected[i].Period.FromTimestamp + actual[i].Period.ToTimestamp = expected[i].Period.ToTimestamp + actual[i].Coordinator.ItemID = expected[i].Coordinator.ItemID + actual[i].Coordinator.EthBlockNum = expected[i].Coordinator.EthBlockNum + assert.Equal(t, expected[i], actual[i]) + } +} diff --git a/api/swagger.yml b/api/swagger.yml index d73ae82..db21d62 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -2714,6 +2714,9 @@ components: items: type: integer example: [32,0,68,21,55,99] + defaultSlotSetBidSlotNum: + type: integer + description: Slot in which the changes will be applied for the first time. outbidding: type: number description: Minimum outbid over the previous one to consider it valid. diff --git a/common/ethauction.go b/common/ethauction.go index 78e8f60..e0f14ba 100644 --- a/common/ethauction.go +++ b/common/ethauction.go @@ -62,7 +62,7 @@ type AuctionVariables struct { // The minimum bid value in a series of 6 slots DefaultSlotSetBid [6]*big.Int `json:"defaultSlotSetBid" meddler:"default_slot_set_bid,json" validate:"required"` // SlotNum at which the new default_slot_set_bid applies - DefaultSlotSetBidSlotNum int64 `json:"-" meddler:"default_slot_set_bid_slot_num"` + DefaultSlotSetBidSlotNum int64 `json:"defaultSlotSetBidSlotNum" meddler:"default_slot_set_bid_slot_num"` // Distance (#slots) to the closest slot to which you can bid ( 2 Slots = 2 * 40 Blocks = 20 min ) ClosedAuctionSlots uint16 `json:"closedAuctionSlots" meddler:"closed_auction_slots" validate:"required"` // Distance (#slots) to the farthest slot to which you can bid (30 days = 4320 slots ) diff --git a/db/historydb/historydb.go b/db/historydb/historydb.go index 799d04a..a8ab28a 100644 --- a/db/historydb/historydb.go +++ b/db/historydb/historydb.go @@ -1701,6 +1701,23 @@ func (hdb *HistoryDB) GetAuctionVars() (*common.AuctionVariables, error) { return auctionVars, tracerr.Wrap(err) } +// GetAuctionVarsUntilSetSlotNum returns all the updates of the auction vars +// from the last entry in which DefaultSlotSetBidSlotNum <= slotNum +func (hdb *HistoryDB) GetAuctionVarsUntilSetSlotNum(slotNum int64, maxItems int) ([]MinBidInfo, error) { + auctionVars := []*MinBidInfo{} + query := ` + SELECT DISTINCT default_slot_set_bid, default_slot_set_bid_slot_num FROM auction_vars + WHERE default_slot_set_bid_slot_num < $1 + ORDER BY default_slot_set_bid_slot_num DESC + LIMIT $2; + ` + err := meddler.QueryAll(hdb.db, &auctionVars, query, slotNum, maxItems) + if err != nil { + return nil, tracerr.Wrap(err) + } + return db.SlicePtrsToSlice(auctionVars).([]MinBidInfo), nil +} + // GetAccountAPI returns an account by its index func (hdb *HistoryDB) GetAccountAPI(idx common.Idx) (*AccountAPI, error) { account := &AccountAPI{} diff --git a/db/historydb/views.go b/db/historydb/views.go index a70b46f..c05d037 100644 --- a/db/historydb/views.go +++ b/db/historydb/views.go @@ -331,3 +331,9 @@ type BidAPI struct { FirstItem uint64 `json:"-" meddler:"first_item"` LastItem uint64 `json:"-" meddler:"last_item"` } + +// MinBidInfo gives information of the minum bid for specific slot(s) +type MinBidInfo struct { + DefaultSlotSetBid [6]*big.Int `json:"defaultSlotSetBid" meddler:"default_slot_set_bid,json" validate:"required"` + DefaultSlotSetBidSlotNum int64 `json:"-" meddler:"default_slot_set_bid_slot_num"` +} diff --git a/test/historydb.go b/test/historydb.go index 2ae4446..725f71c 100644 --- a/test/historydb.go +++ b/test/historydb.go @@ -345,7 +345,7 @@ func GenCoordinators(nCoords int, blocks []common.Block) []common.Coordinator { EthBlockNum: blocks[i%len(blocks)].Num, Forger: ethCommon.BigToAddress(big.NewInt(int64(i))), Bidder: ethCommon.BigToAddress(big.NewInt(int64(i))), - URL: "https://foo.bar", + URL: fmt.Sprintf("https://%d.coord", i), }) } return coords