Browse Source

Merge pull request #428 from hermeznetwork/fix/api-next-forgers

Fix next forgers api response
feature/sql-semaphore1
Eduard S 3 years ago
committed by GitHub
parent
commit
890b94a41a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 226 additions and 74 deletions
  1. +124
    -25
      api/api_test.go
  2. +1
    -1
      api/bids_test.go
  3. +8
    -8
      api/slots_test.go
  4. +44
    -4
      api/state.go
  5. +21
    -34
      api/state_test.go
  6. +3
    -0
      api/swagger.yml
  7. +1
    -1
      common/ethauction.go
  8. +17
    -0
      db/historydb/historydb.go
  9. +6
    -0
      db/historydb/views.go
  10. +1
    -1
      test/historydb.go

+ 124
- 25
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
@ -218,7 +219,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)
chainID := uint16(0)
_config := getConfigTest(chainID)
@ -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)

+ 1
- 1
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)
}

+ 8
- 8
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)

+ 44
- 4
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)

+ 21
- 34
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])
}
}

+ 3
- 0
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.

+ 1
- 1
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 )

+ 17
- 0
db/historydb/historydb.go

@ -1706,6 +1706,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{}

+ 6
- 0
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"`
}

+ 1
- 1
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

Loading…
Cancel
Save