From aeb16d6e3fb9bb51084a4ed0269e12191428cbfb Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 13 Jun 2024 22:54:55 +0200 Subject: [PATCH 1/4] Feed F3 from EC chain and boostrap Signed-off-by: Jakub Sztandera --- cmd/f3/fake_ec.go | 117 ++++++++++++++++++++++++++++++++++++++ cmd/f3/manifest.go | 6 +- cmd/f3/run.go | 5 +- f3.go | 19 ++++++- gpbft/chain.go | 20 +++++-- gpbft/powertable.go | 1 - host.go | 134 +++++++++++++++++++++++++++++++++++++------- manifest.go | 3 +- 8 files changed, 273 insertions(+), 32 deletions(-) create mode 100644 cmd/f3/fake_ec.go diff --git a/cmd/f3/fake_ec.go b/cmd/f3/fake_ec.go new file mode 100644 index 00000000..942d290d --- /dev/null +++ b/cmd/f3/fake_ec.go @@ -0,0 +1,117 @@ +package main + +import ( + "context" + "encoding/binary" + + "golang.org/x/crypto/blake2b" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-f3" + "github.com/filecoin-project/go-f3/gpbft" +) + +type FakeEC struct { + seed []byte + currentEpoch func() int64 + initialPowerTable gpbft.PowerEntries +} + +type Tipset struct { + tsk []byte + epoch int64 +} + +func (ts *Tipset) Key() gpbft.TipSetKey { + return ts.tsk +} + +func (ts *Tipset) Epoch() int64 { + return ts.epoch +} +func (ts *Tipset) Beacon() []byte { + h, err := blake2b.New256([]byte("beacon")) + if err != nil { + panic(err) + } + h.Write(ts.tsk) + return h.Sum(nil) +} + +func NewFakeEC(seed uint64, currentEpoch func() int64, initialPowerTable gpbft.PowerEntries) *FakeEC { + return &FakeEC{ + seed: binary.BigEndian.AppendUint64(nil, seed), + currentEpoch: currentEpoch, + initialPowerTable: initialPowerTable, + } +} + +func (ec *FakeEC) genTipset(epoch int64) *Tipset { + h, err := blake2b.New256(ec.seed) + if err != nil { + panic(err) + } + h.Write(binary.BigEndian.AppendUint64(nil, uint64(epoch))) + rng := h.Sum(nil) + var size uint8 + size, rng = rng[0]%8, rng[1:] + tsk := make([]byte, 0, size*gpbft.CID_MAX_LEN) + + if size == 0 { + return nil + } + + for i := uint8(0); i < size; i++ { + h.Write([]byte{1}) + digest := h.Sum(nil) + if i == 0 { + //encode epoch in the first block hash + binary.BigEndian.PutUint64(digest, uint64(epoch)) + } + tsk = append(tsk, gpbft.DigestToCid(digest)...) + } + return &Tipset{ + tsk: tsk, + epoch: epoch, + } +} + +// GetTipsetByHeight should return a tipset or nil/empty byte array if it does not exists +func (ec *FakeEC) GetTipsetByEpoch(ctx context.Context, epoch int64) (f3.TipSet, error) { + if ec.currentEpoch() < epoch { + return nil, xerrors.Errorf("does not yet exist") + } + ts := ec.genTipset(epoch) + for ts == nil { + epoch-- + ts = ec.genTipset(epoch - 1) + } + return ts, nil +} + +func (ec *FakeEC) GetParent(ctx context.Context, ts f3.TipSet) (f3.TipSet, error) { + + for epoch := ts.Epoch() - 1; epoch > 0; epoch-- { + ts, err := ec.GetTipsetByEpoch(ctx, epoch) + if err != nil { + return nil, xerrors.Errorf("walking back tipsets: %w", err) + } + if ts != nil { + return ts, nil + } + } + return nil, xerrors.Errorf("parent not found") +} + +func (ec *FakeEC) GetHead(ctx context.Context) (f3.TipSet, error) { + return ec.genTipset(ec.currentEpoch()), nil +} + +func (ec *FakeEC) GetPowerTable(ctx context.Context, tsk gpbft.TipSetKey) (gpbft.PowerEntries, error) { + return ec.initialPowerTable, nil +} + +func (ec *FakeEC) GetTipset(ctx context.Context, tsk gpbft.TipSetKey) (f3.TipSet, error) { + epoch := binary.BigEndian.Uint64(tsk[6 : 6+8]) + return ec.genTipset(int64(epoch)), nil +} diff --git a/cmd/f3/manifest.go b/cmd/f3/manifest.go index bb481710..0549b354 100644 --- a/cmd/f3/manifest.go +++ b/cmd/f3/manifest.go @@ -34,7 +34,9 @@ var manifestGenCmd = cli.Command{ path := c.String("manifest") rng := make([]byte, 4) _, _ = rand.Read(rng) - var m f3.Manifest + m := f3.Manifest{ + BootstrapEpoch: 1000, + } m.NetworkName = gpbft.NetworkName(fmt.Sprintf("localnet-%X", rng)) fsig := signing.NewFakeBackend() for i := 0; i < c.Int("N"); i++ { @@ -43,7 +45,7 @@ var manifestGenCmd = cli.Command{ m.InitialPowerTable = append(m.InitialPowerTable, gpbft.PowerEntry{ ID: gpbft.ActorID(i), PubKey: pubkey, - Power: big.NewInt(1), + Power: big.NewInt(1000), }) } f, err := os.OpenFile(path, os.O_WRONLY, 0666) diff --git a/cmd/f3/run.go b/cmd/f3/run.go index ebe04035..952784b2 100644 --- a/cmd/f3/run.go +++ b/cmd/f3/run.go @@ -76,7 +76,10 @@ var runCmd = cli.Command{ signingBackend := &fakeSigner{*signing.NewFakeBackend()} id := c.Uint64("id") signingBackend.Allow(int(id)) - module, err := f3.New(ctx, gpbft.ActorID(id), m, ds, h, ps, signingBackend, signingBackend, nil, log) + + ec := NewFakeEC(1, func() int64 { return 2000 }, m.InitialPowerTable) + module, err := f3.New(ctx, gpbft.ActorID(id), m, ds, h, ps, + signingBackend, signingBackend, ec, log) if err != nil { return xerrors.Errorf("creating module: %w", err) } diff --git a/f3.go b/f3.go index 31a9a52c..ee2874b3 100644 --- a/f3.go +++ b/f3.go @@ -36,6 +36,7 @@ type client struct { certstore *certstore.Store id gpbft.ActorID nn gpbft.NetworkName + ec ECBackend gpbft.Verifier gpbft.SignerWithMarshaler @@ -110,6 +111,7 @@ func New(ctx context.Context, id gpbft.ActorID, manifest Manifest, ds datastore. client: &client{ certstore: cs, + ec: ec, nn: manifest.NetworkName, id: id, Verifier: verif, @@ -231,7 +233,22 @@ loop: return multierr.Append(err, ctx.Err()) } -type ECBackend interface{} +type ECBackend interface { + // GetTipsetByEpoch should return a tipset before the one requested if the requested + // tipset does not exist due to null epochs + GetTipsetByEpoch(ctx context.Context, epoch int64) (TipSet, error) + GetTipset(context.Context, gpbft.TipSetKey) (TipSet, error) + GetHead(context.Context) (TipSet, error) + GetParent(context.Context, TipSet) (TipSet, error) + + GetPowerTable(context.Context, gpbft.TipSetKey) (gpbft.PowerEntries, error) +} + +type TipSet interface { + Key() gpbft.TipSetKey + Beacon() []byte + Epoch() int64 +} type Logger interface { Debug(args ...interface{}) diff --git a/gpbft/chain.go b/gpbft/chain.go index 326ce279..6e1c208a 100644 --- a/gpbft/chain.go +++ b/gpbft/chain.go @@ -3,7 +3,6 @@ package gpbft import ( "bytes" "encoding/binary" - "encoding/hex" "errors" "fmt" "strings" @@ -36,9 +35,14 @@ func MakeCid(data []byte) []byte { // We construct this CID manually to avoid depending on go-cid (it's also a _bit_ faster). digest := blake2b.Sum256(data) - out := make([]byte, 0, 38) + return DigestToCid(digest[:]) +} + +// DigestToCid turns a digest into CBOR + blake2b-256 CID +func DigestToCid(digest []byte) []byte { + out := make([]byte, 0, CID_MAX_LEN) out = append(out, cidPrefix...) - out = append(out, digest[:]...) + out = append(out, digest...) return out } @@ -103,7 +107,7 @@ func (ts *TipSet) String() string { return "" } - return fmt.Sprintf("%d@%s", ts.Epoch, hex.EncodeToString(ts.Key)) + return fmt.Sprintf("@%d", ts.Epoch) } // A chain of tipsets comprising a base (the last finalised tipset from which the chain extends). @@ -263,7 +267,7 @@ func (c ECChain) Validate() error { return fmt.Errorf("tipset %d: %w", i, err) } if ts.Epoch <= lastEpoch { - return errors.New("chain must have increasing epochs") + return fmt.Errorf("chain must have increasing epochs %d <= %d", ts.Epoch, lastEpoch) } lastEpoch = ts.Epoch } @@ -300,5 +304,9 @@ func (c ECChain) String() string { } } b.WriteString("]") - return b.String() + str := b.String() + if len(str) > 77 { + str = str[:77] + "..." + } + return str } diff --git a/gpbft/powertable.go b/gpbft/powertable.go index 4323c659..def2d9e5 100644 --- a/gpbft/powertable.go +++ b/gpbft/powertable.go @@ -84,7 +84,6 @@ func (p PowerEntries) Scaled() (scaled []uint16, total uint16, err error) { // NewPowerTable creates a new PowerTable from a slice of PowerEntry . // It is more efficient than Add, as it only needs to sort the entries once. -// Note that the function takes ownership of the slice - it must not be modified afterwards. func NewPowerTable() *PowerTable { return &PowerTable{ Lookup: make(map[ActorID]int), diff --git a/host.go b/host.go index a8af6b8f..841a5599 100644 --- a/host.go +++ b/host.go @@ -1,15 +1,19 @@ package f3 import ( + "bytes" "context" + "slices" "time" "github.com/filecoin-project/go-f3/certs" "github.com/filecoin-project/go-f3/gpbft" - "github.com/filecoin-project/go-f3/sim" "golang.org/x/xerrors" ) +const instanceLookback = 5 +const finality = 900 + // gpbftRunner is responsible for running gpbft.Participant, taking in all concurrent events and // passing them to gpbft in a single thread. type gpbftRunner struct { @@ -100,6 +104,22 @@ func (h *gpbftRunner) ValidateMessage(msg *gpbft.GMessage) (gpbft.ValidatedMessa return h.participant.ValidateMessage(msg) } +func (h *gpbftHost) collectChain(base TipSet, head TipSet) ([]TipSet, error) { + // TODO: optimize when head is way beyond base + res := make([]TipSet, 0, 2*gpbft.CHAIN_MAX_LEN) + res = append(res, head) + for !bytes.Equal(head.Key(), base.Key()) { + var err error + head, err = h.client.ec.GetParent(h.runningCtx, head) + if err != nil { + return nil, xerrors.Errorf("walking back the chain: %w", err) + } + res = append(res, head) + } + slices.Reverse(res) + return res[1:], nil +} + // Returns inputs to the next GPBFT instance. // These are: // - the supplemental data. @@ -108,40 +128,113 @@ func (h *gpbftRunner) ValidateMessage(msg *gpbft.GMessage) (gpbft.ValidatedMessa // The chain should be a suffix of the last chain notified to the host via // ReceiveDecision (or known to be final via some other channel). func (h *gpbftHost) GetProposalForInstance(instance uint64) (*gpbft.SupplementalData, gpbft.ECChain, error) { - // TODO: this is just a complete fake + var baseTsk gpbft.TipSetKey + if instance == 0 { + ts, err := h.client.ec.GetTipsetByEpoch(h.runningCtx, + h.manifest.BootstrapEpoch-finality) + if err != nil { + return nil, nil, xerrors.Errorf("getting boostrap base: %w", err) + } + baseTsk = ts.Key() + } else { + cert, err := h.client.certstore.Get(h.runningCtx, instance-1) + if err != nil { + return nil, nil, xerrors.Errorf("getting cert for previous instance(%d): %w", instance-1, err) + } + baseTsk = cert.ECChain.Head().Key + } - pt, _, err := h.GetCommitteeForInstance(0) + baseTs, err := h.client.ec.GetTipset(h.runningCtx, baseTsk) if err != nil { - return nil, nil, xerrors.Errorf("getting power table: %w", err) + return nil, nil, xerrors.Errorf("getting base TS: %w", err) + } + headTs, err := h.client.ec.GetHead(h.runningCtx) + if err != nil { + return nil, nil, xerrors.Errorf("getting head TS: %w", err) } - ptCid, err := certs.MakePowerTableCID(pt.Entries) + + collectedChain, err := h.collectChain(baseTs, headTs) if err != nil { - return nil, nil, xerrors.Errorf("computing power table CID: %w", err) + return nil, nil, xerrors.Errorf("collecting chain: %w", err) } - ts := sim.NewTipSetGenerator(1) - chain, err := gpbft.NewChain( - gpbft.TipSet{Epoch: 0, Key: ts.Sample(), PowerTable: ptCid}, - gpbft.TipSet{Epoch: 1, Key: ts.Sample(), PowerTable: ptCid}, - ) + base := gpbft.TipSet{ + Epoch: baseTs.Epoch(), + Key: baseTs.Key(), + } + pte, err := h.client.ec.GetPowerTable(h.runningCtx, baseTs.Key()) if err != nil { - return nil, nil, xerrors.Errorf("geenrating chain: %w", err) + return nil, nil, xerrors.Errorf("getting power table for base: %w", err) } - sd := &gpbft.SupplementalData{ - PowerTable: ptCid, + base.PowerTable, err = certs.MakePowerTableCID(pte) + if err != nil { + return nil, nil, xerrors.Errorf("computing powertable CID for base: %w", err) } - // TODO: use lookback to return the correct next power table commitment and commitments hash. - return sd, chain, nil + suffix := make([]gpbft.TipSet, min(gpbft.CHAIN_MAX_LEN-1, len(collectedChain))) // -1 because of base + for i := 0; i < len(suffix) && i < len(collectedChain); i++ { + suffix[i].Key = collectedChain[i].Key() + suffix[i].Epoch = collectedChain[i].Epoch() + + pte, err = h.client.ec.GetPowerTable(h.runningCtx, suffix[i].Key) + if err != nil { + return nil, nil, xerrors.Errorf("getting power table for suffix %d: %w", i, err) + } + suffix[i].PowerTable, err = certs.MakePowerTableCID(pte) + if err != nil { + return nil, nil, xerrors.Errorf("computing powertable CID for base: %w", err) + } + } + chain, err := gpbft.NewChain(base, suffix...) + if err != nil { + return nil, nil, xerrors.Errorf("making new chain: %w", err) + } + + var supplData gpbft.SupplementalData + pt, _, err := h.GetCommitteeForInstance(instance + 1) + supplData.PowerTable, err = certs.MakePowerTableCID(pt.Entries) + if err != nil { + return nil, nil, xerrors.Errorf("making power table cid for supplemental data: %w", err) + } + + return &supplData, chain, nil } func (h *gpbftHost) GetCommitteeForInstance(instance uint64) (*gpbft.PowerTable, []byte, error) { + var powerTsk gpbft.TipSetKey + + if instance < instanceLookback { + //boostrap phase + ts, err := h.client.ec.GetTipsetByEpoch(h.runningCtx, h.manifest.BootstrapEpoch-finality) + if err != nil { + return nil, nil, xerrors.Errorf("getting tipset for boostrap epoch with lookback: %w", err) + } + powerTsk = ts.Key() + } else { + // TODO: optimize to use saved power tables + cert, err := h.client.certstore.Get(h.runningCtx, instance-instanceLookback) + if err != nil { + return nil, nil, xerrors.Errorf("getting finality certificate: %w", err) + } + powerTsk = cert.ECChain.Head().Key + } + powerEntries, err := h.client.ec.GetPowerTable(h.runningCtx, powerTsk) + if err != nil { + return nil, nil, xerrors.Errorf("getting power table: %w", err) + } + + ts, err := h.client.ec.GetTipset(h.runningCtx, powerTsk) + if err != nil { + return nil, nil, xerrors.Errorf("getting tipset: %w", err) + } + table := gpbft.NewPowerTable() - err := table.Add(h.manifest.InitialPowerTable...) + err = table.Add(powerEntries...) if err != nil { - return nil, nil, err + return nil, nil, xerrors.Errorf("adding entries to power table: %w", err) } - return table, []byte{'A'}, nil + + return table, ts.Beacon(), nil } // Returns the network's name (for signature separation) @@ -184,12 +277,13 @@ func (h *gpbftHost) SetAlarm(at time.Time) { // based on the decision received (which may be in the past). // E.g. this might be: finalised tipset timestamp + epoch duration + stabilisation delay. func (h *gpbftHost) ReceiveDecision(decision *gpbft.Justification) time.Time { - h.log.Infof("got decision: %+v", decision) + h.log.Errorf("got decision, finalized head at epoch: %d", decision.Vote.Value.Head().Epoch) err := h.saveDecision(decision) if err != nil { h.log.Errorf("error while saving decision: %+v", err) } + //TODO: proper timing return time.Now().Add(2 * time.Second) } diff --git a/manifest.go b/manifest.go index 85c5ba44..705c3e7a 100644 --- a/manifest.go +++ b/manifest.go @@ -4,5 +4,6 @@ import "github.com/filecoin-project/go-f3/gpbft" type Manifest struct { NetworkName gpbft.NetworkName - InitialPowerTable []gpbft.PowerEntry + InitialPowerTable gpbft.PowerEntries + BootstrapEpoch int64 } From ee9178707c6311cb09ddae395c9745fa71858bb3 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 13 Jun 2024 22:58:29 +0200 Subject: [PATCH 2/4] Appease linter Signed-off-by: Jakub Sztandera --- cmd/f3/fake_ec.go | 5 +++-- host.go | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/f3/fake_ec.go b/cmd/f3/fake_ec.go index 942d290d..bd551189 100644 --- a/cmd/f3/fake_ec.go +++ b/cmd/f3/fake_ec.go @@ -55,6 +55,7 @@ func (ec *FakeEC) genTipset(epoch int64) *Tipset { rng := h.Sum(nil) var size uint8 size, rng = rng[0]%8, rng[1:] + _ = rng tsk := make([]byte, 0, size*gpbft.CID_MAX_LEN) if size == 0 { @@ -66,7 +67,7 @@ func (ec *FakeEC) genTipset(epoch int64) *Tipset { digest := h.Sum(nil) if i == 0 { //encode epoch in the first block hash - binary.BigEndian.PutUint64(digest, uint64(epoch)) + binary.BigEndian.PutUint64(digest[32-8:], uint64(epoch)) } tsk = append(tsk, gpbft.DigestToCid(digest)...) } @@ -112,6 +113,6 @@ func (ec *FakeEC) GetPowerTable(ctx context.Context, tsk gpbft.TipSetKey) (gpbft } func (ec *FakeEC) GetTipset(ctx context.Context, tsk gpbft.TipSetKey) (f3.TipSet, error) { - epoch := binary.BigEndian.Uint64(tsk[6 : 6+8]) + epoch := binary.BigEndian.Uint64(tsk[6+32-8 : 6+32]) return ec.genTipset(int64(epoch)), nil } diff --git a/host.go b/host.go index 841a5599..69d9b6c0 100644 --- a/host.go +++ b/host.go @@ -192,6 +192,10 @@ func (h *gpbftHost) GetProposalForInstance(instance uint64) (*gpbft.Supplemental var supplData gpbft.SupplementalData pt, _, err := h.GetCommitteeForInstance(instance + 1) + if err != nil { + return nil, nil, xerrors.Errorf("getting commite for %d: %w", instance+1, err) + } + supplData.PowerTable, err = certs.MakePowerTableCID(pt.Entries) if err != nil { return nil, nil, xerrors.Errorf("making power table cid for supplemental data: %w", err) From cd30c14975edb31dfeae832596d73579ab1cad0c Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 14 Jun 2024 18:10:17 +0200 Subject: [PATCH 3/4] Address feedback, add more to manifest, Signed-off-by: Jakub Sztandera Signed-off-by: Jakub Sztandera --- cmd/f3/fake_ec.go | 33 ++++++++++++++++++++++++--------- cmd/f3/manifest.go | 12 ++++-------- cmd/f3/run.go | 2 +- f3.go | 2 ++ gpbft/chain.go | 7 ++++++- host.go | 45 +++++++++++++++++++++++++++++---------------- manifest.go | 32 +++++++++++++++++++++++++++++++- 7 files changed, 97 insertions(+), 36 deletions(-) diff --git a/cmd/f3/fake_ec.go b/cmd/f3/fake_ec.go index bd551189..40d9c1ee 100644 --- a/cmd/f3/fake_ec.go +++ b/cmd/f3/fake_ec.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/binary" + "time" "golang.org/x/crypto/blake2b" "golang.org/x/xerrors" @@ -13,13 +14,16 @@ import ( type FakeEC struct { seed []byte - currentEpoch func() int64 initialPowerTable gpbft.PowerEntries + + ecPeriod time.Duration + ecStart time.Time } type Tipset struct { - tsk []byte - epoch int64 + tsk []byte + epoch int64 + timestamp time.Time } func (ts *Tipset) Key() gpbft.TipSetKey { @@ -38,11 +42,17 @@ func (ts *Tipset) Beacon() []byte { return h.Sum(nil) } -func NewFakeEC(seed uint64, currentEpoch func() int64, initialPowerTable gpbft.PowerEntries) *FakeEC { +func (ts *Tipset) Timestamp() time.Time { + return ts.timestamp +} + +func NewFakeEC(seed uint64, m f3.Manifest) *FakeEC { return &FakeEC{ seed: binary.BigEndian.AppendUint64(nil, seed), - currentEpoch: currentEpoch, - initialPowerTable: initialPowerTable, + initialPowerTable: m.InitialPowerTable, + + ecPeriod: m.ECPeriod, + ecStart: m.ECBoostrapTimestamp, } } @@ -72,11 +82,16 @@ func (ec *FakeEC) genTipset(epoch int64) *Tipset { tsk = append(tsk, gpbft.DigestToCid(digest)...) } return &Tipset{ - tsk: tsk, - epoch: epoch, + tsk: tsk, + epoch: epoch, + timestamp: ec.ecStart.Add(time.Duration(epoch) * ec.ecPeriod), } } +func (ec *FakeEC) currentEpoch() int64 { + return int64(time.Since(ec.ecStart) / ec.ecPeriod) +} + // GetTipsetByHeight should return a tipset or nil/empty byte array if it does not exists func (ec *FakeEC) GetTipsetByEpoch(ctx context.Context, epoch int64) (f3.TipSet, error) { if ec.currentEpoch() < epoch { @@ -105,7 +120,7 @@ func (ec *FakeEC) GetParent(ctx context.Context, ts f3.TipSet) (f3.TipSet, error } func (ec *FakeEC) GetHead(ctx context.Context) (f3.TipSet, error) { - return ec.genTipset(ec.currentEpoch()), nil + return ec.GetTipsetByEpoch(ctx, ec.currentEpoch()) } func (ec *FakeEC) GetPowerTable(ctx context.Context, tsk gpbft.TipSetKey) (gpbft.PowerEntries, error) { diff --git a/cmd/f3/manifest.go b/cmd/f3/manifest.go index 0549b354..b7c94e28 100644 --- a/cmd/f3/manifest.go +++ b/cmd/f3/manifest.go @@ -1,9 +1,7 @@ package main import ( - "crypto/rand" "encoding/json" - "fmt" "math/big" "os" @@ -30,14 +28,11 @@ var manifestGenCmd = cli.Command{ Value: 2, }, }, + Action: func(c *cli.Context) error { path := c.String("manifest") - rng := make([]byte, 4) - _, _ = rand.Read(rng) - m := f3.Manifest{ - BootstrapEpoch: 1000, - } - m.NetworkName = gpbft.NetworkName(fmt.Sprintf("localnet-%X", rng)) + m := f3.LocalnetManifest() + fsig := signing.NewFakeBackend() for i := 0; i < c.Int("N"); i++ { pubkey, _ := fsig.GenerateKey() @@ -48,6 +43,7 @@ var manifestGenCmd = cli.Command{ Power: big.NewInt(1000), }) } + f, err := os.OpenFile(path, os.O_WRONLY, 0666) if err != nil { return xerrors.Errorf("opening manifest file for writing: %w", err) diff --git a/cmd/f3/run.go b/cmd/f3/run.go index 952784b2..7a78c54b 100644 --- a/cmd/f3/run.go +++ b/cmd/f3/run.go @@ -77,7 +77,7 @@ var runCmd = cli.Command{ id := c.Uint64("id") signingBackend.Allow(int(id)) - ec := NewFakeEC(1, func() int64 { return 2000 }, m.InitialPowerTable) + ec := NewFakeEC(1, m) module, err := f3.New(ctx, gpbft.ActorID(id), m, ds, h, ps, signingBackend, signingBackend, ec, log) if err != nil { diff --git a/f3.go b/f3.go index ee2874b3..1505193c 100644 --- a/f3.go +++ b/f3.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "time" "github.com/filecoin-project/go-f3/certstore" "github.com/filecoin-project/go-f3/gpbft" @@ -248,6 +249,7 @@ type TipSet interface { Key() gpbft.TipSetKey Beacon() []byte Epoch() int64 + Timestamp() time.Time } type Logger interface { diff --git a/gpbft/chain.go b/gpbft/chain.go index 6e1c208a..d96ed4df 100644 --- a/gpbft/chain.go +++ b/gpbft/chain.go @@ -2,6 +2,7 @@ package gpbft import ( "bytes" + "encoding/base32" "encoding/binary" "errors" "fmt" @@ -40,6 +41,9 @@ func MakeCid(data []byte) []byte { // DigestToCid turns a digest into CBOR + blake2b-256 CID func DigestToCid(digest []byte) []byte { + if len(digest) != 32 { + panic(fmt.Sprintf("wrong length of digest, expected 32, got %d", len(digest))) + } out := make([]byte, 0, CID_MAX_LEN) out = append(out, cidPrefix...) out = append(out, digest...) @@ -106,8 +110,9 @@ func (ts *TipSet) String() string { if ts == nil { return "" } + encTs := base32.StdEncoding.EncodeToString(ts.Key) - return fmt.Sprintf("@%d", ts.Epoch) + return fmt.Sprintf("%s@%d", encTs[:16], ts.Epoch) } // A chain of tipsets comprising a base (the last finalised tipset from which the chain extends). diff --git a/host.go b/host.go index 69d9b6c0..4960180b 100644 --- a/host.go +++ b/host.go @@ -11,9 +11,6 @@ import ( "golang.org/x/xerrors" ) -const instanceLookback = 5 -const finality = 900 - // gpbftRunner is responsible for running gpbft.Participant, taking in all concurrent events and // passing them to gpbft in a single thread. type gpbftRunner struct { @@ -131,7 +128,7 @@ func (h *gpbftHost) GetProposalForInstance(instance uint64) (*gpbft.Supplemental var baseTsk gpbft.TipSetKey if instance == 0 { ts, err := h.client.ec.GetTipsetByEpoch(h.runningCtx, - h.manifest.BootstrapEpoch-finality) + h.manifest.BootstrapEpoch-h.manifest.ECFinality) if err != nil { return nil, nil, xerrors.Errorf("getting boostrap base: %w", err) } @@ -172,7 +169,7 @@ func (h *gpbftHost) GetProposalForInstance(instance uint64) (*gpbft.Supplemental } suffix := make([]gpbft.TipSet, min(gpbft.CHAIN_MAX_LEN-1, len(collectedChain))) // -1 because of base - for i := 0; i < len(suffix) && i < len(collectedChain); i++ { + for i := range suffix { suffix[i].Key = collectedChain[i].Key() suffix[i].Epoch = collectedChain[i].Epoch() @@ -206,25 +203,37 @@ func (h *gpbftHost) GetProposalForInstance(instance uint64) (*gpbft.Supplemental func (h *gpbftHost) GetCommitteeForInstance(instance uint64) (*gpbft.PowerTable, []byte, error) { var powerTsk gpbft.TipSetKey + var powerEntries gpbft.PowerEntries + var err error - if instance < instanceLookback { + if instance < h.manifest.CommiteeLookback { //boostrap phase - ts, err := h.client.ec.GetTipsetByEpoch(h.runningCtx, h.manifest.BootstrapEpoch-finality) + ts, err := h.client.ec.GetTipsetByEpoch(h.runningCtx, h.manifest.BootstrapEpoch-h.manifest.ECFinality) if err != nil { return nil, nil, xerrors.Errorf("getting tipset for boostrap epoch with lookback: %w", err) } powerTsk = ts.Key() + powerEntries, err = h.client.ec.GetPowerTable(h.runningCtx, powerTsk) + if err != nil { + return nil, nil, xerrors.Errorf("getting power table: %w", err) + } } else { - // TODO: optimize to use saved power tables - cert, err := h.client.certstore.Get(h.runningCtx, instance-instanceLookback) + cert, err := h.client.certstore.Get(h.runningCtx, instance-h.manifest.CommiteeLookback) if err != nil { return nil, nil, xerrors.Errorf("getting finality certificate: %w", err) } powerTsk = cert.ECChain.Head().Key - } - powerEntries, err := h.client.ec.GetPowerTable(h.runningCtx, powerTsk) - if err != nil { - return nil, nil, xerrors.Errorf("getting power table: %w", err) + + powerEntries, err = h.client.certstore.GetPowerTable(h.runningCtx, instance) + if err != nil { + // this fires every round, is this correct? + h.log.Infof("failed getting power table from certstore: %v, falling back to EC", err) + + powerEntries, err = h.client.ec.GetPowerTable(h.runningCtx, powerTsk) + if err != nil { + return nil, nil, xerrors.Errorf("getting power table: %w", err) + } + } } ts, err := h.client.ec.GetTipset(h.runningCtx, powerTsk) @@ -281,14 +290,18 @@ func (h *gpbftHost) SetAlarm(at time.Time) { // based on the decision received (which may be in the past). // E.g. this might be: finalised tipset timestamp + epoch duration + stabilisation delay. func (h *gpbftHost) ReceiveDecision(decision *gpbft.Justification) time.Time { - h.log.Errorf("got decision, finalized head at epoch: %d", decision.Vote.Value.Head().Epoch) + h.log.Infof("got decision, finalized head at epoch: %d", decision.Vote.Value.Head().Epoch) err := h.saveDecision(decision) if err != nil { h.log.Errorf("error while saving decision: %+v", err) } + ts, err := h.client.ec.GetTipset(h.runningCtx, decision.Vote.Value.Head().Key) + if err != nil { + h.log.Errorf("could not get timestamp of just finalized tipset: %+v", err) + return time.Now().Add(h.manifest.ECDelay) + } - //TODO: proper timing - return time.Now().Add(2 * time.Second) + return ts.Timestamp().Add(h.manifest.ECDelay) } func (h *gpbftHost) saveDecision(decision *gpbft.Justification) error { diff --git a/manifest.go b/manifest.go index 705c3e7a..32f32ee1 100644 --- a/manifest.go +++ b/manifest.go @@ -1,9 +1,39 @@ package f3 -import "github.com/filecoin-project/go-f3/gpbft" +import ( + "crypto/rand" + "fmt" + "time" + + "github.com/filecoin-project/go-f3/gpbft" +) type Manifest struct { NetworkName gpbft.NetworkName InitialPowerTable gpbft.PowerEntries BootstrapEpoch int64 + + ECFinality int64 + ECDelay time.Duration + CommiteeLookback uint64 + + //Temporary + ECPeriod time.Duration + ECBoostrapTimestamp time.Time +} + +func LocalnetManifest() Manifest { + rng := make([]byte, 4) + _, _ = rand.Read(rng) + m := Manifest{ + NetworkName: gpbft.NetworkName(fmt.Sprintf("localnet-%X", rng)), + BootstrapEpoch: 1000, + ECFinality: 900, + CommiteeLookback: 5, + ECDelay: 30 * time.Second, + + ECPeriod: 30 * time.Second, + } + m.ECBoostrapTimestamp = time.Now().Add(-time.Duration(m.BootstrapEpoch) * m.ECPeriod) + return m } From 1a50e3e253b6b3ce84a9fe49ce753ef9dae8cefa Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Mon, 17 Jun 2024 14:08:16 +0200 Subject: [PATCH 4/4] Safe truncation Co-authored-by: Steven Allen --- gpbft/chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpbft/chain.go b/gpbft/chain.go index d96ed4df..3c713c30 100644 --- a/gpbft/chain.go +++ b/gpbft/chain.go @@ -112,7 +112,7 @@ func (ts *TipSet) String() string { } encTs := base32.StdEncoding.EncodeToString(ts.Key) - return fmt.Sprintf("%s@%d", encTs[:16], ts.Epoch) + return fmt.Sprintf("%s@%d", encTs[:max(16, len(encTs))], ts.Epoch) } // A chain of tipsets comprising a base (the last finalised tipset from which the chain extends).