Skip to content

Commit

Permalink
fix low gas limit for reading native staking contract (#1852)
Browse files Browse the repository at this point in the history
  • Loading branch information
dustinxie authored Jan 31, 2020
1 parent ac62919 commit b529087
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 24 deletions.
34 changes: 20 additions & 14 deletions action/protocol/poll/nativestaking.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019 IoTeX Foundation
// Copyright (c) 2020 IoTeX Foundation
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
Expand Down Expand Up @@ -79,7 +79,7 @@ func NewNativeStaking(cm protocol.ChainManager, getTipBlockTime GetTipBlockTime)
}

// Votes returns the votes on height
func (ns *NativeStaking) Votes() (*VoteTally, time.Time, error) {
func (ns *NativeStaking) Votes(correctGas bool) (*VoteTally, time.Time, error) {
if ns.contract == "" {
return nil, time.Time{}, ErrNoData
}
Expand All @@ -97,52 +97,57 @@ func (ns *NativeStaking) Votes() (*VoteTally, time.Time, error) {
limit := big.NewInt(256)

for {
vote, err := ns.readBuckets(prevIndex, limit)
vote, index, err := ns.readBuckets(prevIndex, limit, correctGas)
log.L().Debug("Read native buckets from contract", zap.Int("size", len(vote)))
if err == ErrEndOfData {
// all data been read
break
}
if err != nil {
log.L().Error(" read native staking contract", zap.Error(err))
return nil, now, err
}
votes.tally(vote, now)
if len(vote) < int(limit.Int64()) {
// all data been read
break
}
prevIndex.Add(prevIndex, limit)
prevIndex = index
}
return &votes, now, nil
}

func (ns *NativeStaking) readBuckets(prevIndx, limit *big.Int) ([]*types.Bucket, error) {
func (ns *NativeStaking) readBuckets(prevIndx, limit *big.Int, correctGas bool) ([]*types.Bucket, *big.Int, error) {
data, err := ns.abi.Pack("getActivePyggs", prevIndx, limit)
if err != nil {
return nil, err
return nil, nil, err
}

// read the staking contract
ex, err := action.NewExecution(ns.contract, 1, big.NewInt(0), 1000000, big.NewInt(0), data)
gasLimit := uint64(1000000)
if correctGas {
gasLimit *= 10
}
ex, err := action.NewExecution(ns.contract, 1, big.NewInt(0), gasLimit, big.NewInt(0), data)
if err != nil {
return nil, err
return nil, nil, err
}
data, _, err = ns.cm.ExecuteContractRead(dummyCaller, ex)
if err != nil {
return nil, err
return nil, nil, err
}

// decode the contract read result
pygg := &pygg{}
if err = ns.abi.Unpack(pygg, "getActivePyggs", data); err != nil {
if err.Error() == "abi: unmarshalling empty output" {
// no data in contract (one possible reason is that contract does not exist yet)
return nil, ErrNoData
return nil, nil, ErrNoData
}
return nil, err
return nil, nil, err
}
if len(pygg.CanNames) == 0 {
return nil, ErrEndOfData
return nil, nil, ErrEndOfData
}
buckets := make([]*types.Bucket, len(pygg.CanNames))
for i := range pygg.CanNames {
Expand All @@ -155,10 +160,11 @@ func (ns *NativeStaking) readBuckets(prevIndx, limit *big.Int) ([]*types.Bucket,
pygg.Decays[i],
)
if err != nil {
return nil, err
return nil, nil, err
}
}
return buckets, nil
// last one of returned indexes should be used as starting index for next query
return buckets, pygg.Indexes[len(pygg.Indexes)-1], nil
}

// SetContract sets the contract address
Expand Down
46 changes: 45 additions & 1 deletion action/protocol/poll/nativestaking_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// Copyright (c) 2020 IoTeX Foundation
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
// License 2.0 that can be found in the LICENSE file.

package poll

import (
Expand Down Expand Up @@ -66,7 +72,7 @@ func TestStaking(t *testing.T) {
)
}

tallies := VoteTally{
tallies := &VoteTally{
Candidates: make(map[[12]byte]*state.Candidate),
Buckets: make([]*types.Bucket, 0),
}
Expand All @@ -82,6 +88,44 @@ func TestStaking(t *testing.T) {
}
}
require.Equal(len(buckets), len(tallies.Buckets))
for i := range buckets {
require.Equal(buckets[i], tallies.Buckets[i])
}

// merge with existing data
cand := state.CandidateList{
&state.Candidate{
CanName: []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Votes: big.NewInt(111),
},
&state.Candidate{
CanName: []byte{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Votes: big.NewInt(111),
},
&state.Candidate{
CanName: []byte{107, 111, 118, 97, 110, 114, 111, 98, 111, 116, 50, 56},
Votes: big.NewInt(111),
},
&state.Candidate{
CanName: []byte{107, 111, 118, 97, 110, 114, 111, 98, 111, 116, 50, 55},
Votes: big.NewInt(111),
},
}

amount = big.NewInt(unit.Iotx)
sc := &stakingCommittee{
nativeStaking: ns,
scoreThreshold: amount.Mul(amount, big.NewInt(200)),
}

newCand := sc.mergeDelegates(cand, tallies, time.Now())
require.Equal(2, len(newCand))
for i := range newCand {
name := cand[i].CanName
require.Equal(name, newCand[i].CanName)
cand[i].Votes.Add(cand[i].Votes, tallies.Candidates[to12Bytes(name)].Votes)
require.Equal(cand[i].Votes, newCand[i].Votes)
}

// test empty data from contract
require.NoError(ns.abi.Unpack(pygg, "getActivePyggs", empty))
Expand Down
4 changes: 2 additions & 2 deletions action/protocol/poll/staking_committee.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019 IoTeX Foundation
// Copyright (c) 2020 IoTeX Foundation
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
Expand Down Expand Up @@ -113,7 +113,7 @@ func (sc *stakingCommittee) DelegatesByHeight(height uint64) (state.CandidateLis
if sc.nativeStaking == nil {
return nil, errors.New("native staking was not set after cook height")
}
nativeVotes, ts, err := sc.nativeStaking.Votes()
nativeVotes, ts, err := sc.nativeStaking.Votes(sc.hu.IsPost(config.Daytona, height))
if err == ErrNoData {
// no native staking data
return sc.filterDelegates(cand), nil
Expand Down
5 changes: 4 additions & 1 deletion blockchain/genesis/genesis.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019 IoTeX Foundation
// Copyright (c) 2020 IoTeX Foundation
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
Expand Down Expand Up @@ -53,6 +53,7 @@ func defaultConfig() Genesis {
BeringBlockHeight: 1512001,
CookBlockHeight: 1641601,
DardanellesBlockHeight: 1816201,
DaytonaBlockHeight: 3238921,
},
Account: Account{
InitBalanceMap: make(map[string]string),
Expand Down Expand Up @@ -132,6 +133,8 @@ type (
CookBlockHeight uint64 `yaml:"cookHeight"`
// DardanellesBlockHeight is the start height of 5s block internal
DardanellesBlockHeight uint64 `yaml:"dardanellesHeight"`
// DaytonaBlockHeight is the height to fix low gas for read native staking contract
DaytonaBlockHeight uint64 `yaml:"daytonaBlockHeight"`
}
// Account contains the configs for account protocol
Account struct {
Expand Down
15 changes: 14 additions & 1 deletion config/heightupgrade.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019 IoTeX
// Copyright (c) 2020 IoTeX
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
Expand All @@ -17,19 +17,26 @@ const (
Bering
Cook
Dardanelles
Daytona
)

type (
// HeightName is codename for height upgrades
HeightName int

// HeightUpgrade lists heights at which certain fixes take effect
// prior to Dardanelles, each epoch consists of 360 sub-epochs
// so height = 360k + 1
// starting Dardanelles, each epoch consists of 720 sub-epochs
// however, DardanellesHeight is set to 360(2k + 1) + 1 (instead of 720k + 1)
// so height afterwards must be set to 360(2k + 1) + 1
HeightUpgrade struct {
pacificHeight uint64
aleutianHeight uint64
beringHeight uint64
cookHeight uint64
dardanellesHeight uint64
daytonaHeight uint64
}
)

Expand All @@ -41,6 +48,7 @@ func NewHeightUpgrade(cfg Config) HeightUpgrade {
cfg.Genesis.BeringBlockHeight,
cfg.Genesis.CookBlockHeight,
cfg.Genesis.DardanellesBlockHeight,
cfg.Genesis.DaytonaBlockHeight,
}
}

Expand All @@ -58,6 +66,8 @@ func (hu *HeightUpgrade) IsPost(name HeightName, height uint64) bool {
h = hu.cookHeight
case Dardanelles:
h = hu.dardanellesHeight
case Daytona:
h = hu.daytonaHeight
default:
log.Panic("invalid height name!")
}
Expand All @@ -83,3 +93,6 @@ func (hu *HeightUpgrade) CookBlockHeight() uint64 { return hu.cookHeight }

// DardanellesBlockHeight returns the dardanelles height
func (hu *HeightUpgrade) DardanellesBlockHeight() uint64 { return hu.dardanellesHeight }

// DaytonaBlockHeight returns the daytona height
func (hu *HeightUpgrade) DaytonaBlockHeight() uint64 { return hu.daytonaHeight }
18 changes: 13 additions & 5 deletions config/heightupgrade_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2019 IoTeX
// Copyright (c) 2020 IoTeX
// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no
// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent
// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache
Expand All @@ -19,13 +19,17 @@ func TestNewHeightChange(t *testing.T) {
require.Equal(1, Aleutian)
require.Equal(2, Bering)
require.Equal(3, Cook)
require.Equal(4, Dardanelles)
require.Equal(5, Daytona)
cfg := Default
cfg.Genesis.PacificBlockHeight = uint64(432001)
hu := NewHeightUpgrade(cfg)
require.Equal(uint64(432001), hu.pacificHeight)
require.Equal(uint64(864001), hu.aleutianHeight)
require.Equal(uint64(1512001), hu.beringHeight)
require.Equal(uint64(1641601), hu.cookHeight)
require.Equal(uint64(432001), hu.PacificBlockHeight())
require.Equal(uint64(864001), hu.AleutianBlockHeight())
require.Equal(uint64(1512001), hu.BeringBlockHeight())
require.Equal(uint64(1641601), hu.CookBlockHeight())
require.Equal(uint64(1816201), hu.DardanellesBlockHeight())
require.Equal(uint64(3238921), hu.DaytonaBlockHeight())

require.True(hu.IsPre(Pacific, uint64(432000)))
require.True(hu.IsPost(Pacific, uint64(432001)))
Expand All @@ -35,4 +39,8 @@ func TestNewHeightChange(t *testing.T) {
require.True(hu.IsPost(Bering, uint64(1512001)))
require.True(hu.IsPre(Cook, uint64(1641600)))
require.True(hu.IsPost(Cook, uint64(1641601)))
require.True(hu.IsPre(Dardanelles, uint64(1816200)))
require.True(hu.IsPost(Dardanelles, uint64(1816201)))
require.True(hu.IsPre(Daytona, uint64(3238920)))
require.True(hu.IsPost(Daytona, uint64(3238921)))
}
190 changes: 190 additions & 0 deletions e2etest/staking_test.go

Large diffs are not rendered by default.

0 comments on commit b529087

Please sign in to comment.