Skip to content

Commit

Permalink
Commit to a next power table with additional commitments
Browse files Browse the repository at this point in the history
In addition to committing to the power table for each tipset, we commit
to the power table used to validate the next instance in the instance
root (along with a merkle-tree of arbitrary commitments).

fixes #257
  • Loading branch information
Stebalien committed May 24, 2024
1 parent add8260 commit 11ce69c
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 10 deletions.
1 change: 1 addition & 0 deletions gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func main() {
err := gen.WriteTupleEncodersToFile("../gpbft/gen.go", "gpbft",
gpbft.TipSet{},
gpbft.GMessage{},
gpbft.InstanceData{},
gpbft.Payload{},
gpbft.Justification{},
)
Expand Down
128 changes: 126 additions & 2 deletions gpbft/gen.go

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

34 changes: 31 additions & 3 deletions gpbft/gpbft.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ type Justification struct {
Signature []byte
}

type InstanceData struct {
// The blake2b hash of the power table used to validate the next instance, taking lookback
// into account.
PowerTable [32]byte
// Merkle-tree of instance-specific commitments. Currently empty but this will eventually
// include things like snark-friendly power-table commitments.
Commitments [32]byte
}

func (d *InstanceData) Eq(other *InstanceData) bool {
return d.Commitments == other.Commitments &&
d.PowerTable == other.PowerTable
}

// Fields of the message that make up the signature payload.
type Payload struct {
// GossiPBFT instance (epoch) number.
Expand All @@ -86,15 +100,18 @@ type Payload struct {
Round uint64
// GossiPBFT step name.
Step Phase
// Chain of tipsets proposed/voted for finalisation.
// Always non-empty; the first entry is the base tipset finalised in the previous instance.
// The common data. XXX Rename
Data InstanceData
// The value agreed-upon in a single instance.
// XXX: rename to chain
Value ECChain
}

func (p *Payload) Eq(other *Payload) bool {
return p.Instance == other.Instance &&
p.Round == other.Round &&
p.Step == other.Step &&
p.Data.Eq(&other.Data) &&
p.Value.Eq(other.Value)
}

Expand All @@ -114,6 +131,8 @@ func (p *Payload) MarshalForSigning(nn NetworkName) []byte {
_ = binary.Write(&buf, binary.BigEndian, p.Step)
_ = binary.Write(&buf, binary.BigEndian, p.Round)
_ = binary.Write(&buf, binary.BigEndian, p.Instance)
_, _ = buf.Write(p.Data.Commitments[:])
_, _ = buf.Write(p.Data.PowerTable[:])
_, _ = buf.Write(root[:])
return buf.Bytes()
}
Expand Down Expand Up @@ -165,6 +184,8 @@ type instance struct {
// Decision state. Collects DECIDE messages until a decision can be made,
// independently of protocol phases/rounds.
decision *quorumState
// Instance data that all participants must agree on.
instanceData *InstanceData
// tracer traces logic logs for debugging and simulation purposes.
tracer Tracer
}
Expand All @@ -173,6 +194,7 @@ func newInstance(
participant *Participant,
instanceID uint64,
input ECChain,
data *InstanceData,
powerTable PowerTable,
beacon []byte) (*instance, error) {
if input.IsZero() {
Expand All @@ -193,7 +215,8 @@ func newInstance(
rounds: map[uint64]*roundState{
0: newRoundState(powerTable),
},
decision: newQuorumState(powerTable),
instanceData: data,
decision: newQuorumState(powerTable),
}, nil
}

Expand Down Expand Up @@ -379,6 +402,11 @@ func (i *instance) validateMessage(msg *GMessage) error {
if msg.Vote.Instance != i.instanceID {
return xerrors.Errorf("message for wrong instance %d, expected %d", msg.Vote.Instance, i.instanceID)
}
// Ensure all participants are proposing the same instance data. This data is based on the
// _base_, so everyone should agree.
if !msg.Vote.Data.Eq(i.instanceData) {
return xerrors.Errorf("message for instance %d disagrees on the instance data", msg.Vote.Instance)
}
// Check sender is eligible.
senderPower, senderPubKey := i.powerTable.Get(msg.Sender)
if senderPower == nil || senderPower.Sign() == 0 {
Expand Down
5 changes: 4 additions & 1 deletion gpbft/participant.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,13 @@ func (p *Participant) beginInstance() error {
if err != nil {
return fmt.Errorf("failed fetching power table for instance %d: %w", p.nextInstance, err)
}
// XXX: Need the the power table for the next instance after this one:
// GetCommitteeForInstance(p.nextInstance + 1)
data := new(InstanceData)
if err := power.Validate(); err != nil {
return fmt.Errorf("invalid power table: %w", err)
}
if p.granite, err = newInstance(p, p.nextInstance, chain, *power, beacon); err != nil {
if p.granite, err = newInstance(p, p.nextInstance, chain, data, *power, beacon); err != nil {
return fmt.Errorf("failed creating new granite instance: %w", err)
}
p.nextInstance += 1
Expand Down
14 changes: 10 additions & 4 deletions gpbft/signature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@ func TestPayloadMarshalForSigning(t *testing.T) {
Instance: 1,
Round: 2,
Step: 3,
Value: nil, // not valid in f3, but an empty merkle-tree is defined to hash to all zeros.
Data: gpbft.InstanceData{
Commitments: [32]byte{0x42},
PowerTable: [32]byte{0x43},
},
Value: nil, // not valid in f3, but an empty merkle-tree is defined to hash to all zeros.
}).MarshalForSigning(nn)
require.Len(t, encoded, 64)
require.Len(t, encoded, 128)
assert.Equal(t, encoded[:15], []byte("GPBFT:filecoin:")) // separators
assert.Equal(t, encoded[15], uint8(3)) // step
assert.Equal(t, binary.BigEndian.Uint64(encoded[16:24]), uint64(2)) // round
assert.Equal(t, binary.BigEndian.Uint64(encoded[24:32]), uint64(1)) // instance (32-byte right aligned)
assert.Equal(t, encoded[32:64], make([]byte, 32)) // 32-byte aligned merkle-tree root
assert.EqualValues(t, encoded[32:64], [32]byte{0x42}) // commitments root
assert.EqualValues(t, encoded[64:96], [32]byte{0x43}) // next power table
assert.EqualValues(t, encoded[96:128], [32]byte{}) // tipsets

// Simulate a signe decide message, the one we'll need to verify as a part of finality certificates.
encoded = (&gpbft.Payload{
Expand All @@ -33,7 +39,7 @@ func TestPayloadMarshalForSigning(t *testing.T) {
Step: gpbft.DECIDE_PHASE,
Value: nil, // not valid in f3, but an empty merkle-tree is defined to hash to all zeros.
}).MarshalForSigning(nn)
expected := make([]byte, 64)
expected := make([]byte, 128)

// We expect it to be prefixed with "GPBFT:filecoin:\x05
copy(expected, []byte("GPBFT:filecoin:\x05"))
Expand Down
2 changes: 2 additions & 0 deletions sim/signing/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ func (v *FakeBackend) MarshalPayloadForSigning(nn gpbft.NetworkName, p *gpbft.Pa
_ = binary.Write(&buf, binary.BigEndian, p.Step)
_ = binary.Write(&buf, binary.BigEndian, p.Round)
_ = binary.Write(&buf, binary.BigEndian, p.Instance)
_, _ = buf.Write(p.Data.Commitments[:])
_, _ = buf.Write(p.Data.PowerTable[:])
_ = binary.Write(&buf, binary.BigEndian, uint32(len(p.Value)))
for i := range p.Value {
ts := &p.Value[i]
Expand Down

0 comments on commit 11ce69c

Please sign in to comment.