Skip to content

Commit

Permalink
Consistently use Merkle Tree root as ECChain key
Browse files Browse the repository at this point in the history
Remove the custom ECChain key generation inside `gpbft` in favor of
consistently using Merkle Tree root of chain as key. This key is already
used for signature payload generation, and in the new chain exchange as
a key to identify a chain. Doing so enables signature validation of
messages without having to know the chain.

Consistent use of Merkle Tree root hash as key enables a number of
simplifications across the repo:
* unblocks the unification of validation logic between partial and full
  validators, since caching can use a consistent way to identify
  messages whether they're partial or not.
* reduce various type gymnastics across root and chain exchange packages
  by repurposing gpbft ECChain Key.

The work here also introduces lazy-loading for the ECChain, which
requires ECChain type to be converted to `struct`, and be passed by
pointer. As part of this work, TipSet is also turned into a pointer
consistently across various interfaces.

The ECChain receiver functions are adopted to accommodate potential nil
values for a chain.

Fixes #825
  • Loading branch information
masih committed Jan 24, 2025
1 parent 5c916d1 commit d15b409
Show file tree
Hide file tree
Showing 56 changed files with 941 additions and 650 deletions.
16 changes: 7 additions & 9 deletions cbor_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions certchain/certchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var (
type FinalityCertificateProvider func(context.Context, uint64) (*certs.FinalityCertificate, error)

type tipSetWithPowerTable struct {
gpbft.TipSet
*gpbft.TipSet
Beacon []byte
PowerTable *gpbft.PowerTable
}
Expand All @@ -31,7 +31,7 @@ type CertChain struct {

rng *rand.Rand
certificates []*certs.FinalityCertificate
generateProposal func(context.Context, uint64) (gpbft.ECChain, error)
generateProposal func(context.Context, uint64) (*gpbft.ECChain, error)
}

func New(o ...Option) (*CertChain, error) {
Expand Down Expand Up @@ -63,7 +63,7 @@ func (cc *CertChain) GetCommittee(instance uint64) (*gpbft.Committee, error) {
return cc.getCommittee(tspt)
}

func (cc *CertChain) GetProposal(instance uint64) (*gpbft.SupplementalData, gpbft.ECChain, error) {
func (cc *CertChain) GetProposal(instance uint64) (*gpbft.SupplementalData, *gpbft.ECChain, error) {
//TODO refactor ProposalProvider in gpbft to take context.
ctx := context.TODO()
proposal, err := cc.generateProposal(ctx, instance)
Expand Down Expand Up @@ -120,7 +120,7 @@ func (cc *CertChain) getTipSetWithPowerTableByEpoch(ctx context.Context, epoch i
return nil, err
}
return &tipSetWithPowerTable{
TipSet: gpbft.TipSet{
TipSet: &gpbft.TipSet{
Epoch: epoch,
Key: ts.Key(),
PowerTable: ptCid,
Expand All @@ -130,12 +130,12 @@ func (cc *CertChain) getTipSetWithPowerTableByEpoch(ctx context.Context, epoch i
}, nil
}

func (cc *CertChain) generateRandomProposal(ctx context.Context, base gpbft.TipSet, len int) (gpbft.ECChain, error) {
func (cc *CertChain) generateRandomProposal(ctx context.Context, base *gpbft.TipSet, len int) (*gpbft.ECChain, error) {
if len == 0 {
return gpbft.NewChain(base)
}

suffix := make([]gpbft.TipSet, len-1)
suffix := make([]*gpbft.TipSet, len-1)
for i := range suffix {
epoch := base.Epoch + 1 + int64(i)
gTS, err := cc.getTipSetWithPowerTableByEpoch(ctx, epoch)
Expand Down Expand Up @@ -250,7 +250,7 @@ func (cc *CertChain) sign(ctx context.Context, committee *gpbft.Committee, paylo
func (cc *CertChain) Generate(ctx context.Context, length uint64) ([]*certs.FinalityCertificate, error) {
cc.certificates = make([]*certs.FinalityCertificate, 0, length)

cc.generateProposal = func(ctx context.Context, instance uint64) (gpbft.ECChain, error) {
cc.generateProposal = func(ctx context.Context, instance uint64) (*gpbft.ECChain, error) {
var baseEpoch int64
if instance == cc.m.InitialInstance {
baseEpoch = cc.m.BootstrapEpoch - cc.m.EC.Finality
Expand Down
2 changes: 1 addition & 1 deletion certexchange/polling/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const TestNetworkName gpbft.NetworkName = "testnet"

func MakeCertificate(t *testing.T, rng *rand.Rand, tsg *sim.TipSetGenerator, backend signing.Backend, base *gpbft.TipSet, instance uint64, powerTable, nextPowerTable gpbft.PowerEntries) *certs.FinalityCertificate {
chainLen := rng.Intn(23) + 1
chain, err := gpbft.NewChain(*base)
chain, err := gpbft.NewChain(base)
require.NoError(t, err)

for i := 0; i < chainLen; i++ {
Expand Down
24 changes: 21 additions & 3 deletions certexchange/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,23 @@ func TestClientServer(t *testing.T) {
cs, err := certstore.CreateStore(ctx, ds, 0, pt)
require.NoError(t, err)

cert := &certs.FinalityCertificate{GPBFTInstance: 0, SupplementalData: supp, ECChain: gpbft.ECChain{{Epoch: 0, Key: gpbft.TipSetKey("tsk0"), PowerTable: pcid}}}
cert := &certs.FinalityCertificate{GPBFTInstance: 0, SupplementalData: supp,
ECChain: &gpbft.ECChain{
TipSets: []*gpbft.TipSet{
{Epoch: 0, Key: gpbft.TipSetKey("tsk0"), PowerTable: pcid},
},
},
}
err = cs.Put(ctx, cert)
require.NoError(t, err)

cert = &certs.FinalityCertificate{GPBFTInstance: 1, SupplementalData: supp, ECChain: gpbft.ECChain{{Epoch: 0, Key: gpbft.TipSetKey("tsk0"), PowerTable: pcid}}}
cert = &certs.FinalityCertificate{GPBFTInstance: 1, SupplementalData: supp,
ECChain: &gpbft.ECChain{
TipSets: []*gpbft.TipSet{
{Epoch: 0, Key: gpbft.TipSetKey("tsk0"), PowerTable: pcid},
},
},
}
err = cs.Put(ctx, cert)
require.NoError(t, err)

Expand Down Expand Up @@ -149,7 +161,13 @@ func TestClientServer(t *testing.T) {
}

// Until we've added a new certificate.
cert = &certs.FinalityCertificate{GPBFTInstance: 2, SupplementalData: supp, ECChain: gpbft.ECChain{{Epoch: 0, Key: gpbft.TipSetKey("tsk0"), PowerTable: pcid}}}
cert = &certs.FinalityCertificate{GPBFTInstance: 2, SupplementalData: supp,
ECChain: &gpbft.ECChain{
TipSets: []*gpbft.TipSet{
{Epoch: 0, Key: gpbft.TipSetKey("tsk0"), PowerTable: pcid},
},
},
}
require.NoError(t, cs.Put(ctx, cert))

{
Expand Down
61 changes: 16 additions & 45 deletions certs/cbor_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions certs/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type FinalityCertificate struct {
GPBFTInstance uint64
// The ECChain finalized during this instance, starting with the last tipset finalized in
// the previous instance.
ECChain gpbft.ECChain
ECChain *gpbft.ECChain
// Additional data signed by the participants in this instance. Currently used to certify
// the power table used in the next instance.
SupplementalData gpbft.SupplementalData
Expand Down Expand Up @@ -89,7 +89,7 @@ func NewFinalityCertificate(powerDelta PowerTableDiff, justification *gpbft.Just
// finalized, the instance of the first invalid finality certificate, and the power table that
// should be used to validate that finality certificate, along with the error encountered.
func ValidateFinalityCertificates(verifier gpbft.Verifier, network gpbft.NetworkName, prevPowerTable gpbft.PowerEntries, nextInstance uint64, base *gpbft.TipSet,
certs ...*FinalityCertificate) (_nextInstance uint64, chain gpbft.ECChain, newPowerTable gpbft.PowerEntries, err error) {
certs ...*FinalityCertificate) (_nextInstance uint64, chain *gpbft.ECChain, newPowerTable gpbft.PowerEntries, err error) {
for _, cert := range certs {
if cert.GPBFTInstance != nextInstance {
return nextInstance, chain, prevPowerTable, fmt.Errorf("expected instance %d, found instance %d", nextInstance, cert.GPBFTInstance)
Expand Down Expand Up @@ -133,7 +133,7 @@ func ValidateFinalityCertificates(verifier gpbft.Verifier, network gpbft.Network
cert.GPBFTInstance, cert.SupplementalData.PowerTable, powerTableCid)
}
nextInstance++
chain = append(chain, cert.ECChain.Suffix()...)
chain = chain.Append(cert.ECChain.Suffix()...)
prevPowerTable = newPowerTable
base = cert.ECChain.Head()
}
Expand Down
20 changes: 11 additions & 9 deletions certs/certs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func TestFinalityCertificates(t *testing.T) {

rng := rand.New(rand.NewSource(1234))
tsg := sim.NewTipSetGenerator(rng.Uint64())
base := gpbft.TipSet{Epoch: 0, Key: tsg.Sample(), PowerTable: tableCid}
base := &gpbft.TipSet{Epoch: 0, Key: tsg.Sample(), PowerTable: tableCid}

certificates := make([]*certs.FinalityCertificate, 10)
powerTables := make([]gpbft.PowerEntries, 10)
Expand All @@ -224,14 +224,14 @@ func TestFinalityCertificates(t *testing.T) {
cert, err := certs.NewFinalityCertificate(certs.MakePowerTableDiff(powerTables[i], powerTable), justification)
require.NoError(t, err)
certificates[i] = cert
base = *justification.Vote.Value.Head()
base = justification.Vote.Value.Head()
}

// Validate one.
nextInstance, chain, newPowerTable, err := certs.ValidateFinalityCertificates(backend, networkName, powerTables[0], 0, certificates[0].ECChain.Base(), certificates[0])
require.NoError(t, err)
require.EqualValues(t, 1, nextInstance)
require.True(t, chain.Eq(certificates[0].ECChain.Suffix()))
require.Equal(t, chain.TipSets, certificates[0].ECChain.Suffix())
require.Equal(t, powerTables[1], newPowerTable)

// Validate multiple
Expand All @@ -240,14 +240,14 @@ func TestFinalityCertificates(t *testing.T) {
require.EqualValues(t, 4, nextInstance)
require.Equal(t, powerTables[4], newPowerTable)
require.True(t, certificates[3].ECChain.Head().Equal(chain.Head()))
require.True(t, certificates[0].ECChain[1].Equal(chain.Base()))
require.True(t, certificates[0].ECChain.TipSets[1].Equal(chain.Base()))

nextInstance, chain, newPowerTable, err = certs.ValidateFinalityCertificates(backend, networkName, powerTables[nextInstance], nextInstance, nil, certificates[nextInstance:]...)
require.NoError(t, err)
require.EqualValues(t, len(certificates), nextInstance)
require.Equal(t, powerTable, newPowerTable)
require.True(t, certificates[len(certificates)-1].ECChain.Head().Equal(chain.Head()))
require.True(t, certificates[4].ECChain[1].Equal(chain.Base()))
require.True(t, certificates[4].ECChain.TipSets[1].Equal(chain.Base()))
}

func TestBadFinalityCertificates(t *testing.T) {
Expand All @@ -257,7 +257,7 @@ func TestBadFinalityCertificates(t *testing.T) {
tsg := sim.NewTipSetGenerator(rng.Uint64())
tableCid, err := certs.MakePowerTableCID(powerTable)
require.NoError(t, err)
base := gpbft.TipSet{Epoch: 0, Key: tsg.Sample(), PowerTable: tableCid}
base := &gpbft.TipSet{Epoch: 0, Key: tsg.Sample(), PowerTable: tableCid}

nextPowerTable, _ := randomizePowerTable(rng, backend, 200, powerTable, nil)

Expand Down Expand Up @@ -393,8 +393,10 @@ func TestBadFinalityCertificates(t *testing.T) {
// Chain is invalid.
{
certCpy := *certificate
certCpy.ECChain = slices.Clone(certCpy.ECChain)
slices.Reverse(certCpy.ECChain)
certCpy.ECChain = &gpbft.ECChain{
TipSets: slices.Clone(certificate.ECChain.TipSets),
}
slices.Reverse(certCpy.ECChain.TipSets)
nextInstance, chain, newPowerTable, err := certs.ValidateFinalityCertificates(backend, networkName, powerTable, 1, nil, &certCpy)
require.ErrorContains(t, err, "chain must have increasing epochs")
require.EqualValues(t, 1, nextInstance)
Expand Down Expand Up @@ -476,7 +478,7 @@ func randomPowerTable(backend signing.Backend, entries int64) gpbft.PowerEntries
return powerTable
}

func makeJustification(t *testing.T, rng *rand.Rand, tsg *sim.TipSetGenerator, backend signing.Backend, base gpbft.TipSet, instance uint64, powerTable, nextPowerTable gpbft.PowerEntries) *gpbft.Justification {
func makeJustification(t *testing.T, rng *rand.Rand, tsg *sim.TipSetGenerator, backend signing.Backend, base *gpbft.TipSet, instance uint64, powerTable, nextPowerTable gpbft.PowerEntries) *gpbft.Justification {
chainLen := rng.Intn(23) + 1
chain, err := gpbft.NewChain(base)
require.NoError(t, err)
Expand Down
6 changes: 5 additions & 1 deletion certstore/certstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ func makeCert(instance uint64, supp gpbft.SupplementalData) *certs.FinalityCerti
return &certs.FinalityCertificate{
GPBFTInstance: instance,
SupplementalData: supp,
ECChain: gpbft.ECChain{{Epoch: 0, Key: gpbft.TipSetKey("tsk0"), PowerTable: supp.PowerTable}},
ECChain: &gpbft.ECChain{
TipSets: []*gpbft.TipSet{
{Epoch: 0, Key: gpbft.TipSetKey("tsk0"), PowerTable: supp.PowerTable},
},
},
}
}

Expand Down
15 changes: 3 additions & 12 deletions chainexchange/chainexchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,18 @@ import (
"github.com/filecoin-project/go-f3/gpbft"
)

type Key []byte

type Keyer interface {
Key(gpbft.ECChain) Key
}

type Message struct {
Instance uint64
Chain gpbft.ECChain
Chain *gpbft.ECChain
Timestamp int64
}

type ChainExchange interface {
Keyer
Broadcast(context.Context, Message) error
GetChainByInstance(context.Context, uint64, Key) (gpbft.ECChain, bool)
GetChainByInstance(context.Context, uint64, gpbft.ECChainKey) (*gpbft.ECChain, bool)
RemoveChainsByInstance(context.Context, uint64) error
}

type Listener interface {
NotifyChainDiscovered(ctx context.Context, key Key, instance uint64, chain gpbft.ECChain)
NotifyChainDiscovered(ctx context.Context, instance uint64, chain *gpbft.ECChain)
}

func (k Key) IsZero() bool { return len(k) == 0 }
Loading

0 comments on commit d15b409

Please sign in to comment.