Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions pkg/chain/ethereum/beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,26 @@ func (bc *BeaconChain) IsOperatorInPool() (bool, error) {
return bc.randomBeacon.IsOperatorInPool(bc.key.Address)
}

// IsOperatorUpToDate checks if the operator's authorized stake is in sync
// with operator's weight in the sortition pool.
// If the operator's authorized stake is not in sync with sortition pool
// weight, function returns false.
// If the operator is not in the sortition pool and their authorized stake
// is non-zero, function returns false.
func (bc *BeaconChain) IsOperatorUpToDate() (bool, error) {
return bc.randomBeacon.IsOperatorUpToDate(bc.key.Address)
}

// JoinSortitionPool executes a transaction to have the current operator join
// the sortition pool.
func (bc *BeaconChain) JoinSortitionPool() error {
_, err := bc.randomBeacon.JoinSortitionPool()
return err
}

// UpdateOperatorStatus executes a transaction to update the current
// operator's state in the sortition pool.
func (bc *BeaconChain) UpdateOperatorStatus() error {
_, err := bc.randomBeacon.UpdateOperatorStatus(bc.key.Address)
return err
}
12 changes: 12 additions & 0 deletions pkg/sortition/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@ type Chain interface {
// the sortition pool.
IsOperatorInPool() (bool, error)

// IsOperatorUpToDate checks if the operator's authorized stake is in sync
// with operator's weight in the sortition pool.
// If the operator's authorized stake is not in sync with sortition pool
// weight, function returns false.
// If the operator is not in the sortition pool and their authorized stake
// is non-zero, function returns false.
IsOperatorUpToDate() (bool, error)

// JoinSortitionPool executes a transaction to have the current operator
// join the sortition pool.
JoinSortitionPool() error

// UpdateOperatorStatus executes a transaction to update the current
// operator's state in the sortition pool.
UpdateOperatorStatus() error
}
44 changes: 42 additions & 2 deletions pkg/sortition/internal/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ type localChain struct {
operatorToStakingProvider map[chain.Address]chain.Address
operatorToStakingProviderMutex sync.RWMutex

// Stake for an operator, as set in the Sortition Pool contract.
// Weight of an operator, as set in the Sortition Pool contract.
sortitionPool map[chain.Address]*big.Int
sortitionPoolMutex sync.RWMutex

// Stake for an operator, as set in the Token Staking contract,
// Stake for a staking provider, as set in the Token Staking contract,
// minus the pending authorization decrease.
eligibleStake map[chain.Address]*big.Int
eligibleStakeMutex sync.RWMutex
Expand Down Expand Up @@ -88,6 +88,28 @@ func (lc *localChain) IsOperatorInPool() (bool, error) {
return ok, nil
}

func (lc *localChain) IsOperatorUpToDate() (bool, error) {
lc.sortitionPoolMutex.RLock()
defer lc.sortitionPoolMutex.RUnlock()

lc.eligibleStakeMutex.RLock()
defer lc.eligibleStakeMutex.RUnlock()

stakingProvider, isRegistered := lc.operatorToStakingProvider[lc.operatorAddress]
if !isRegistered {
return false, errOperatorUnknown
}

eligibleStake, hasStake := lc.eligibleStake[stakingProvider]
weight, isInPool := lc.sortitionPool[lc.operatorAddress]

if !isInPool {
return !hasStake || eligibleStake.Cmp(big.NewInt(0)) == 0, nil
} else {
return weight.Cmp(eligibleStake) == 0, nil
}
}

func (lc *localChain) JoinSortitionPool() error {
lc.operatorToStakingProviderMutex.Lock()
defer lc.operatorToStakingProviderMutex.Unlock()
Expand All @@ -114,3 +136,21 @@ func (lc *localChain) JoinSortitionPool() error {

return nil
}

func (lc *localChain) UpdateOperatorStatus() error {
lc.eligibleStakeMutex.RLock()
defer lc.eligibleStakeMutex.RUnlock()

lc.sortitionPoolMutex.Lock()
defer lc.sortitionPoolMutex.Unlock()

stakingProvider, isRegistered := lc.operatorToStakingProvider[lc.operatorAddress]
if !isRegistered {
return errOperatorUnknown
}

eligibleStake := lc.eligibleStake[stakingProvider]
lc.sortitionPool[lc.operatorAddress] = eligibleStake

return nil
}
128 changes: 128 additions & 0 deletions pkg/sortition/internal/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,96 @@ func TestEligibleStake(t *testing.T) {
)
}

func TestOperatorUpToDate_NotRegisteredOperator(t *testing.T) {
localChain := connectLocal(testOperatorAddress)

_, err := localChain.IsOperatorUpToDate()
testutils.AssertErrorsEqual(t, errOperatorUnknown, err)
}

func TestOperatorUpToDate_NoStake(t *testing.T) {
localChain := connectLocal(testOperatorAddress)
localChain.registerOperator(testStakingProviderAddress, testOperatorAddress)

isUpToDate, err := localChain.IsOperatorUpToDate()
if err != nil {
t.Fatal(err)
}

if !isUpToDate {
t.Fatal("expected the operator to be up to date")
}
}

func TestOperatorUpToDate_ZeroStake(t *testing.T) {
localChain := connectLocal(testOperatorAddress)
localChain.registerOperator(testStakingProviderAddress, testOperatorAddress)
localChain.setEligibleStake(testStakingProviderAddress, big.NewInt(0))

isUpToDate, err := localChain.IsOperatorUpToDate()
if err != nil {
t.Fatal(err)
}

if !isUpToDate {
t.Fatal("expected the operator to be up to date")
}
}

func TestOperatorUpToDate_NonZeroStakeNotInPool(t *testing.T) {
localChain := connectLocal(testOperatorAddress)
localChain.registerOperator(testStakingProviderAddress, testOperatorAddress)
localChain.setEligibleStake(testStakingProviderAddress, big.NewInt(100))

isUpToDate, err := localChain.IsOperatorUpToDate()
if err != nil {
t.Fatal(err)
}

if isUpToDate {
t.Fatal("expected the operator not to be up to date")
}
}

func TestOperatorUpToDate_StakeInSyncWithWeight(t *testing.T) {
localChain := connectLocal(testOperatorAddress)
localChain.registerOperator(testStakingProviderAddress, testOperatorAddress)
localChain.setEligibleStake(testStakingProviderAddress, big.NewInt(100))
err := localChain.JoinSortitionPool()
if err != nil {
t.Fatal(err)
}

isUpToDate, err := localChain.IsOperatorUpToDate()
if err != nil {
t.Fatal(err)
}

if !isUpToDate {
t.Fatal("expected the operator to be up to date")
}
}

func TestOperatorUpToDate_StakeNotInSyncWithWeight(t *testing.T) {
localChain := connectLocal(testOperatorAddress)
localChain.registerOperator(testStakingProviderAddress, testOperatorAddress)
localChain.setEligibleStake(testStakingProviderAddress, big.NewInt(100))
err := localChain.JoinSortitionPool()
if err != nil {
t.Fatal(err)
}
localChain.setEligibleStake(testStakingProviderAddress, big.NewInt(101))

isUpToDate, err := localChain.IsOperatorUpToDate()
if err != nil {
t.Fatal(err)
}

if isUpToDate {
t.Fatal("expected the operator not to be up to date")
}
}

func TestJoinSortitionPool_NotRegisteredOperator(t *testing.T) {
localChain := connectLocal(testOperatorAddress)

Expand Down Expand Up @@ -112,3 +202,41 @@ func TestJoinSortitionPool_OperatorAlreadyInPool(t *testing.T) {
err = localChain.JoinSortitionPool()
testutils.AssertErrorsEqual(t, errOperatorAlreadyRegisteredInPool, err)
}

func TestUpdateOperatorStatus_NotRegisteredOperator(t *testing.T) {
localChain := connectLocal(testOperatorAddress)

err := localChain.UpdateOperatorStatus()
testutils.AssertErrorsEqual(t, errOperatorUnknown, err)
}

func TestUpdateOperatorStatus(t *testing.T) {
localChain := connectLocal(testOperatorAddress)
localChain.registerOperator(testStakingProviderAddress, testOperatorAddress)
localChain.setEligibleStake(testStakingProviderAddress, big.NewInt(100))
err := localChain.JoinSortitionPool()
if err != nil {
t.Fatal(err)
}
localChain.setEligibleStake(testStakingProviderAddress, big.NewInt(101))

isUpToDate, err := localChain.IsOperatorUpToDate()
if err != nil {
t.Fatal(err)
}

if isUpToDate {
t.Fatal("expected the operator not to be up to date")
}

localChain.UpdateOperatorStatus()

isUpToDate, err = localChain.IsOperatorUpToDate()
if err != nil {
t.Fatal(err)
}

if !isUpToDate {
t.Fatal("expected the operator to be up to date")
}
}