Skip to content
This repository has been archived by the owner on Jan 3, 2023. It is now read-only.

Initial reconfiguration mechanism for Mir #234

Merged
merged 64 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
0ecda24
draft restarter
dnkolegov Jul 21, 2022
33ebce3
refactor draft
dnkolegov Jul 21, 2022
453d8df
drafting manager with new transport
dnkolegov Jul 22, 2022
11f4643
Update Mir manager
dnkolegov Jul 28, 2022
37aa456
fix gomod
dnkolegov Jul 28, 2022
9f183cf
Merge branch 'eudico' into mir-node-restarter
dnkolegov Jul 28, 2022
f6d1b45
fix gitignore
dnkolegov Jul 28, 2022
338b6c3
drafting reconfiguration mechanism
dnkolegov Aug 2, 2022
0e29004
refactor the draft
dnkolegov Aug 3, 2022
1960ea3
continue drafting the code
dnkolegov Aug 3, 2022
3d0a392
drafting tests for reconfiguration
dnkolegov Aug 4, 2022
85ecee6
use updates channel
dnkolegov Aug 4, 2022
bdd2590
fix data race
dnkolegov Aug 5, 2022
70fa770
adopt Mir's chat app reconfiguration
dnkolegov Aug 8, 2022
447b3aa
the first working version
dnkolegov Aug 9, 2022
728f013
adopt lasr mir's reconfiguration version
dnkolegov Aug 9, 2022
add7a36
sync and add test
dnkolegov Aug 9, 2022
74fc3a3
add roundrobin test consensus to test mir configuration
dnkolegov Aug 10, 2022
1200dc3
combine dummy consensus and round robin
dnkolegov Aug 10, 2022
feb135d
fox scripts
dnkolegov Aug 10, 2022
83a4819
more tests
dnkolegov Aug 10, 2022
4ebd4c6
fix description
dnkolegov Aug 11, 2022
1a1d7e5
update readmes
dnkolegov Aug 11, 2022
e79f810
add scenario
dnkolegov Aug 11, 2022
c5b862d
start reconnect in goroutine
dnkolegov Aug 11, 2022
ffa7d89
clean the code
dnkolegov Aug 12, 2022
954baf7
adopt last mir version
dnkolegov Aug 12, 2022
4f149ab
fix misprint
dnkolegov Aug 12, 2022
93752e5
clean Eudico
dnkolegov Aug 13, 2022
cb3af17
fix CI complainsobjections
dnkolegov Aug 13, 2022
8914a08
fix panic
dnkolegov Aug 13, 2022
b8895b9
fix copy of map
dnkolegov Aug 15, 2022
8a7287c
clean the code
dnkolegov Aug 15, 2022
5859ec5
remove mir logs
dnkolegov Aug 17, 2022
c01300f
don't use reconfiguration in the rootnet
dnkolegov Aug 17, 2022
19e55a3
fix review comments - part 1
dnkolegov Aug 17, 2022
39901d6
use weak quorum function
dnkolegov Aug 17, 2022
1eed733
use map to store memberships
dnkolegov Aug 17, 2022
5d32acb
move reconfiguration to mine function
dnkolegov Aug 17, 2022
a014449
move epoch miner computation and clean manager
dnkolegov Aug 17, 2022
d1bf811
remove locking from state manager
dnkolegov Aug 18, 2022
dbf62a2
remove locking from state manager
dnkolegov Aug 18, 2022
01fd274
fix macos CI version
dnkolegov Aug 21, 2022
30c718e
Change and signal validator set on leaving
dnkolegov Aug 22, 2022
082bf50
fix actor unit tests
dnkolegov Aug 22, 2022
9429c28
fix data race in subnet manager
dnkolegov Aug 22, 2022
46e276f
fix linting
dnkolegov Aug 22, 2022
cdb2c8e
fix another data race found by the reconfig test
dnkolegov Aug 22, 2022
528c0dc
fix test tools
dnkolegov Aug 23, 2022
0127d41
fix old tools
dnkolegov Aug 23, 2022
2b703e3
integrate Mir's availability
dnkolegov Aug 23, 2022
907b05f
go mod tidy
dnkolegov Aug 23, 2022
2ab9450
clean the code
dnkolegov Aug 24, 2022
bb3463c
Update chain/consensus/mir/state_manager.go
dnkolegov Aug 25, 2022
e4f7dc0
Merge branch 'eudico' into mir-node-restarter
dnkolegov Aug 25, 2022
516fbb4
remove SubmitInterval
dnkolegov Aug 25, 2022
12fceca
compare validator sets
dnkolegov Aug 25, 2022
d5c991c
try to fix data races
dnkolegov Aug 26, 2022
33e837e
add test
dnkolegov Aug 26, 2022
b460210
fix linting
dnkolegov Aug 26, 2022
da30e9d
create a function to spawn a side subnet
dnkolegov Aug 26, 2022
0857892
remove todos
dnkolegov Aug 26, 2022
2e43571
fix conflicts
dnkolegov Aug 27, 2022
6fb898b
make mempool to print messages in debug mode only
dnkolegov Aug 29, 2022
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ bin/tmp/*
scratchpad
gen.gen

**/*-wal/
**/*-wal-*/
.devcontainer
/eudico-stats
*.key
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -417,3 +417,7 @@ print-%:

circleci:
go generate -x ./.circleci

.PHONY: lint
lint:
golangci-lint run ./...
14 changes: 7 additions & 7 deletions chain/consensus/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Consensus Interface

`Consensus interface` is an interface that is used to agree on the next block using the concrete implementation of a consensus protocol.
Eudico has several implementations of different BFT-type and Nakamoto-type consensus protocols for research purposes.
`Consensus` interface is an interface that is used to agree on the next block using the concrete implementation of a consensus protocol.
Eudico uses several implementations of different BFT-type and Nakamoto-type consensus protocols for research purposes.

## How to Add a New Consensus Protocol to Eudico
1. Register a consensus type constant in `chain/consensus/hierarchical/types.go`
2. Instantiate a consensus and the corresponding miner for a subnet via `New()`, `Weight()`, and `Mine()` functions of `chain/consensus/hierarchical/subnet/consensus/consensus.go`
3. Create a new file with implementations of genesis block functions in `chain/consensus/hierarchical/actors/subnet/` and in `chain/consensus/hierarchical/actors/subnet/genesis.go`
4. Implement the `Consensus` interface defined in `chain/consensus/iface.go` for the target consensus protocol
5. Add the corresponding CLI commands in `cmd/eudico/$CONSENSUS.go`
1. Register a consensus type constant in `chain/consensus/hierarchical/types.go` and add name, finality, checkpoint period values in the corresponding functions.
2. Instantiate a consensus and the corresponding miner for a subnet via `New()`, `Weight()`, and `Mine()` functions of `chain/consensus/hierarchical/subnet/consensus/consensus.go`.
3. Create a new file with implementations of genesis block functions in `chain/consensus/hierarchical/actors/subnet/` and in `chain/consensus/hierarchical/actors/subnet/genesis.go`.
4. Implement the `Consensus` interface defined in `chain/consensus/iface.go` for the target consensus protocol.
5. Add the corresponding CLI commands in `cmd/eudico/$CONSENSUS.go`.
6. Adapt [bad blocks cache](https://github.com/filecoin-project/eudico/blob/0306742e553f6bd6260332b501bb65a5bfc16a76/chain/sync.go#L725) for the consensus protocol if needed.
For example, it may be necessary to not process blocks if consensus RPCs are unreachable.
7. Add a new consensus code to the consensus usage message in `cmd/eudico/subnet.go`.
Expand Down
6 changes: 3 additions & 3 deletions chain/consensus/delegcns/mine.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ func Mine(ctx context.Context, addr address.Address, api v1api.FullNode) error {
timer := time.NewTicker(time.Duration(build.BlockDelaySecs) * time.Second)
for {
select {
case <-ctx.Done():
log.Debug("Delegcns miner: context closed")
return nil
case <-timer.C:
base, err := api.ChainHead(ctx)
if err != nil {
Expand Down Expand Up @@ -96,10 +99,7 @@ func Mine(ctx context.Context, addr address.Address, api v1api.FullNode) error {
log.Errorw("submitting block failed", "error", err)
continue
}

log.Info("delegated mined a block! ", bh.Cid(), " msgs ", len(msgs))
case <-ctx.Done():
return nil
}
}
}
Expand Down
64 changes: 50 additions & 14 deletions chain/consensus/dummy/dummy.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Package dummy implements consensus for testing purposes only.
// Dummy consensus is a centralised consensus, it works on one node only,
// and fundamentally cannot be extended to run on multiple nodes.
// Package dummy implements dummy insecure non-robust consensus protocol for testing and demo purposes only.
// Dummy consensus has the following properties: permissioned, leader-based, round-robin, non-crash tolerant.
// It works as follows:
// 1. All nodes have the same (agreed) validator set.
// 2. The first validator from this set is the permanent leader.
// 3. The leader proposes blocks, the block miner is assigned using round-robin.
// 4. Signing is not used.
package dummy

import (
Expand Down Expand Up @@ -36,7 +40,7 @@ import (
)

const (
MaxHeightDrift = 5
ValidatorsEnv = "EUDICO_DUMMY_VALIDATORS"
)

var (
Expand Down Expand Up @@ -69,7 +73,7 @@ func NewConsensus(
if err != nil {
return nil, err
}
log.Infof("New Dummy consensus for %s subnet", subnetID)
log.Infof("New dummy consensus for %s subnet", subnetID)

return &Dummy{
store: sm.ChainStore(),
Expand Down Expand Up @@ -110,7 +114,7 @@ func (bft *Dummy) ValidateBlock(ctx context.Context, b *types.FullBlock) (err er
log.Warn("Got block from the future, but within threshold", h.Timestamp, build.Clock.Now().Unix())
}

msgsChecks := common.CheckMsgs(ctx, bft.store, bft.sm, bft.subMgr, bft.resolver, bft.netName, b, baseTs)
msgsChecks := common.CheckMsgsWithoutBlockSig(ctx, bft.store, bft.sm, bft.subMgr, bft.resolver, bft.netName, b, baseTs)

minerCheck := async.Err(func() error {
if err := bft.minerIsValid(b.Header.Miner); err != nil {
Expand Down Expand Up @@ -170,7 +174,7 @@ func (bft *Dummy) ValidateBlock(ctx context.Context, b *types.FullBlock) (err er

func (bft *Dummy) ValidateBlockPubsub(ctx context.Context, self bool, msg *pubsub.Message) (pubsub.ValidationResult, string) {
if self {
return common.ValidateLocalBlock(ctx, msg)
return validateLocalBlock(ctx, msg)
}

// track validation time
Expand All @@ -186,7 +190,7 @@ func (bft *Dummy) ValidateBlockPubsub(ctx context.Context, self bool, msg *pubsu
panic(what)
}

blk, what, err := common.DecodeAndCheckBlock(msg)
blk, what, err := decodeAndCheckBlock(msg)
if err != nil {
log.Error("got invalid block over pubsub: ", err)
recordFailureFlagPeer(what)
Expand Down Expand Up @@ -221,12 +225,7 @@ func (bft *Dummy) minerIsValid(maddr address.Address) error {
// We are currently using defaults here and not worrying about it.
// We will consider potential changes of Consensus interface in https://github.com/filecoin-project/eudico/issues/143.
func (bft *Dummy) IsEpochBeyondCurrMax(epoch abi.ChainEpoch) bool {
if bft.genesis == nil {
return false
}

now := uint64(build.Clock.Now().Unix())
return epoch > (abi.ChainEpoch((now-bft.genesis.MinTimestamp())/build.BlockDelaySecs) + MaxHeightDrift)
return false
}

func (bft *Dummy) Type() hierarchical.ConsensusType {
Expand All @@ -242,3 +241,40 @@ func Weight(ctx context.Context, stateBs bstore.Blockstore, ts *types.TipSet) (t

return big.NewInt(int64(ts.Height() + 1)), nil
}

func validateLocalBlock(ctx context.Context, msg *pubsub.Message) (pubsub.ValidationResult, string) {
stats.Record(ctx, metrics.BlockPublished.M(1))

if size := msg.Size(); size > 1<<20-1<<15 {
log.Errorf("ignoring oversize block (%dB)", size)
return pubsub.ValidationIgnore, "oversize_block"
}

blk, what, err := decodeAndCheckBlock(msg)
if err != nil {
log.Errorf("got invalid local block: %s", err)
return pubsub.ValidationIgnore, what
}

msg.ValidatorData = blk
stats.Record(ctx, metrics.BlockValidationSuccess.M(1))
return pubsub.ValidationAccept, ""
}

func decodeAndCheckBlock(msg *pubsub.Message) (*types.BlockMsg, string, error) {
blk, err := types.DecodeBlockMsg(msg.GetData())
if err != nil {
return nil, "invalid", xerrors.Errorf("error decoding block: %w", err)
}

if count := len(blk.BlsMessages) + len(blk.SecpkMessages); count > build.BlockMessageLimit {
return nil, "too_many_messages", xerrors.Errorf("block contains too many messages (%d)", count)
}

// make sure we have a signature
if blk.Header.BlockSig != nil {
return nil, "missing_signature", xerrors.Errorf("block with a signature")
}

return blk, "", nil
}
63 changes: 47 additions & 16 deletions chain/consensus/dummy/mine.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package dummy

import (
"context"
"fmt"
"os"
"time"

"github.com/filecoin-project/go-address"
lapi "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/api/v1api"
"github.com/filecoin-project/lotus/chain/consensus/common"
"github.com/filecoin-project/lotus/chain/consensus/hierarchical"
"github.com/filecoin-project/lotus/chain/consensus/platform/logging"
"github.com/filecoin-project/lotus/chain/types"
)
Expand All @@ -24,22 +27,40 @@ func Mine(ctx context.Context, miner address.Address, api v1api.FullNode) error
return err
}

submitting := time.NewTicker(300 * time.Millisecond)
defer submitting.Stop()
validatorsEnv := os.Getenv(ValidatorsEnv)
var validators []address.Address
validators, err = validatorsFromString(validatorsEnv)
if err != nil {
return fmt.Errorf("failed to get validators addresses: %w", err)
}
if len(validators) == 0 {
validators = append(validators, miner)
}

for {
base, err := api.ChainHead(ctx)
if err != nil {
log.Errorw("failed to get the head of chain", "error", err)
continue
}
submit := time.NewTicker(300 * time.Millisecond)
defer submit.Stop()

leader := validators[0]

for {
select {
case <-ctx.Done():
log.Debug("Dummy miner %s: context closed")
log.Debug("Dummy miner: context closed")
return nil

case <-submitting.C:
case <-submit.C:
base, err := api.ChainHead(ctx)
if err != nil {
log.Errorw("failed to get the head of chain", "error", err)
continue
}

if miner != leader {
continue
}

epochMiner := validators[int(base.Height())%len(validators)]

msgs, err := api.MpoolSelect(ctx, base.Key(), 1)
if err != nil {
log.Errorw("unable to select messages from mempool", "error", err)
Expand All @@ -54,7 +75,7 @@ func Mine(ctx context.Context, miner address.Address, api v1api.FullNode) error

log.Infof("[subnet: %s, epoch: %d] try to create a block", subnetID, base.Height()+1)
bh, err := api.MinerCreateBlock(ctx, &lapi.BlockTemplate{
Miner: miner,
Miner: epochMiner,
Parents: base.Key(),
BeaconValues: nil,
Ticket: nil,
Expand Down Expand Up @@ -96,10 +117,20 @@ func (bft *Dummy) CreateBlock(ctx context.Context, w lapi.Wallet, bt *lapi.Block
return nil, err
}

err = common.SignBlock(ctx, w, b)
if err != nil {
return nil, err
}

return b, nil
}

// validatorsFromString parses comma-separated validator addresses string.
//
// Examples of the validator string: "t1wpixt5mihkj75lfhrnaa6v56n27epvlgwparujy,t1wpixt5mihkj75lfhrnaa6v56n27epvlgwparujy"
func validatorsFromString(input string) ([]address.Address, error) {
var addrs []address.Address
for _, id := range hierarchical.SplitAndTrimEmpty(input, ",", " ") {
a, err := address.NewFromString(id)
if err != nil {
return nil, fmt.Errorf("failed to parse %v: %w", id, err)
}
addrs = append(addrs, a)
}
return addrs, nil
}
17 changes: 16 additions & 1 deletion chain/consensus/hierarchical/actors/subnet/subnet_actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ func (a SubnetActor) Join(rt runtime.Runtime, v *hierarchical.Validator) *abi.Em
func (a SubnetActor) Leave(rt runtime.Runtime, _ *abi.EmptyValue) *abi.EmptyValue {
rt.ValidateImmediateCallerAcceptAny()
sourceAddr := rt.Caller()
sourceSecpAddr := sca.SecpBLSAddr(rt, rt.Caller())

var (
st SubnetState
Expand Down Expand Up @@ -203,8 +204,13 @@ func (a SubnetActor) Leave(rt runtime.Runtime, _ *abi.EmptyValue) *abi.EmptyValu
priorBalance := rt.CurrentBalance()
var retFunds abi.TokenAmount
rt.StateTransaction(&st, func() {
// Remove stake from stake balanace table.
// Remove stake from stake balance table.
retFunds = st.rmStake(rt, sourceAddr, stakes, minerStake)

// Remove the validator to signal to other validators on the subnet to run reconfiguration.
if st.ValidatorSet != nil {
dnkolegov marked this conversation as resolved.
Show resolved Hide resolved
st.ValidatorSet = rmValidator(sourceSecpAddr, st.ValidatorSet)
}
})

// Never send back if we don't have enough balance
Expand Down Expand Up @@ -484,6 +490,15 @@ func (st *SubnetState) rmStake(rt runtime.Runtime, sourceAddr address.Address, s
return retFunds
}

func rmValidator(validator address.Address, validators []hierarchical.Validator) []hierarchical.Validator {
for i, v := range validators {
if v.Addr == validator {
return append(validators[:i], validators[i+1:]...)
}
}
return validators
}

func rmMiner(miner address.Address, ls []address.Address) []address.Address {
for i, v := range ls {
if v == miner {
Expand Down
7 changes: 7 additions & 0 deletions chain/consensus/hierarchical/actors/subnet/subnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,11 @@ func TestLeaveAndKill(t *testing.T) {
rt := getRuntime(t)
h.constructAndVerify(t, rt)
joiner := tutil.NewIDAddr(t, 102)
joinerSecp := tutil.NewSECP256K1Addr(h.t, joiner.String())
joiner2 := tutil.NewIDAddr(t, 103)
joiner2Secp := tutil.NewSECP256K1Addr(h.t, joiner2.String())
joiner3 := tutil.NewIDAddr(t, 104)
joiner3Secp := tutil.NewSECP256K1Addr(h.t, joiner3.String())
totalStake := abi.NewTokenAmount(0)

t.Log("first miner joins subnet")
Expand Down Expand Up @@ -171,6 +174,7 @@ func TestLeaveAndKill(t *testing.T) {
minerStake := getStake(t, rt, joiner2)
totalStake = big.Sub(totalStake, minerStake)
rt.SetBalance(minerStake)
rt.ExpectSend(joiner2, builtin.MethodsAccount.PubkeyAddress, nil, big.Zero(), &joiner2Secp, exitcode.Ok)
rt.ExpectSend(hierarchical.SubnetCoordActorAddr, sca.Methods.ReleaseStake, &sca.FundParams{Value: minerStake}, big.Zero(), nil, exitcode.Ok)
rt.ExpectSend(joiner2, builtin.MethodSend, nil, big.Div(minerStake, actor.LeavingFeeCoeff), nil, exitcode.Ok)
rt.Call(h.SubnetActor.Leave, nil)
Expand All @@ -194,6 +198,7 @@ func TestLeaveAndKill(t *testing.T) {
minerStake = getStake(t, rt, joiner)
totalStake = big.Sub(totalStake, minerStake)
rt.SetBalance(minerStake)
rt.ExpectSend(joiner, builtin.MethodsAccount.PubkeyAddress, nil, big.Zero(), &joinerSecp, exitcode.Ok)
rt.ExpectSend(hierarchical.SubnetCoordActorAddr, sca.Methods.ReleaseStake, &sca.FundParams{Value: minerStake}, big.Zero(), nil, exitcode.Ok)
rt.ExpectSend(joiner, builtin.MethodSend, nil, big.Div(minerStake, actor.LeavingFeeCoeff), nil, exitcode.Ok)
rt.Call(h.SubnetActor.Leave, nil)
Expand All @@ -206,6 +211,7 @@ func TestLeaveAndKill(t *testing.T) {

t.Log("miner can't leave twice")
rt.ExpectValidateCallerAny()
rt.ExpectSend(joiner, builtin.MethodsAccount.PubkeyAddress, nil, big.Zero(), &joinerSecp, exitcode.Ok)
rt.ExpectAbort(exitcode.ErrForbidden, func() {
rt.Call(h.SubnetActor.Leave, nil)
})
Expand All @@ -223,6 +229,7 @@ func TestLeaveAndKill(t *testing.T) {

t.Log("subnet can't be killed twice")
rt.ExpectValidateCallerAny()
rt.ExpectSend(joiner3, builtin.MethodsAccount.PubkeyAddress, nil, big.Zero(), &joiner3Secp, exitcode.Ok)
rt.ExpectAbort(exitcode.ErrIllegalState, func() {
rt.Call(h.SubnetActor.Kill, nil)
})
Expand Down
Loading