This repository has been archived by the owner on Oct 31, 2024. It is now read-only.
forked from 0xPolygon/polygon-edge
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow min commitment of single state sync event (0xPolygon#1475)
* Allow min commitment of single state sync event * Fix assertion in checkStateSyncResultLogs * Change assertions so that multiple deposit batches test can be run independently * Remove unnecesary cast * Add UT * Reorganize files * Fix UT * Remove constant * Fix --------- Co-authored-by: Goran Rojovic <goran.rojovic@ethernal.tech>
- Loading branch information
1 parent
96d0d89
commit 7890d28
Showing
8 changed files
with
418 additions
and
391 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
package polybft | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" | ||
"github.com/0xPolygon/polygon-edge/crypto" | ||
"github.com/0xPolygon/polygon-edge/merkle-tree" | ||
"github.com/0xPolygon/polygon-edge/state/runtime/precompiled" | ||
"github.com/0xPolygon/polygon-edge/types" | ||
) | ||
|
||
const ( | ||
stTypeBridgeCommitment = "commitment" | ||
stTypeEndEpoch = "end-epoch" | ||
) | ||
|
||
// PendingCommitment holds merkle trie of bridge transactions accompanied by epoch number | ||
type PendingCommitment struct { | ||
*contractsapi.StateSyncCommitment | ||
MerkleTree *merkle.MerkleTree | ||
Epoch uint64 | ||
} | ||
|
||
// NewPendingCommitment creates a new commitment object | ||
func NewPendingCommitment(epoch uint64, stateSyncEvents []*contractsapi.StateSyncedEvent) (*PendingCommitment, error) { | ||
tree, err := createMerkleTree(stateSyncEvents) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &PendingCommitment{ | ||
MerkleTree: tree, | ||
Epoch: epoch, | ||
StateSyncCommitment: &contractsapi.StateSyncCommitment{ | ||
StartID: stateSyncEvents[0].ID, | ||
EndID: stateSyncEvents[len(stateSyncEvents)-1].ID, | ||
Root: tree.Hash(), | ||
}, | ||
}, nil | ||
} | ||
|
||
// Hash calculates hash value for commitment object. | ||
func (cm *PendingCommitment) Hash() (types.Hash, error) { | ||
data, err := cm.StateSyncCommitment.EncodeAbi() | ||
if err != nil { | ||
return types.Hash{}, err | ||
} | ||
|
||
return crypto.Keccak256Hash(data), nil | ||
} | ||
|
||
var _ contractsapi.StateTransactionInput = &CommitmentMessageSigned{} | ||
|
||
// CommitmentMessageSigned encapsulates commitment message with aggregated signatures | ||
type CommitmentMessageSigned struct { | ||
Message *contractsapi.StateSyncCommitment | ||
AggSignature Signature | ||
PublicKeys [][]byte | ||
} | ||
|
||
// Hash calculates hash value for commitment object. | ||
func (cm *CommitmentMessageSigned) Hash() (types.Hash, error) { | ||
data, err := cm.Message.EncodeAbi() | ||
if err != nil { | ||
return types.Hash{}, err | ||
} | ||
|
||
return crypto.Keccak256Hash(data), nil | ||
} | ||
|
||
// VerifyStateSyncProof validates given state sync proof | ||
// against merkle tree root hash contained in the CommitmentMessage | ||
func (cm *CommitmentMessageSigned) VerifyStateSyncProof(proof []types.Hash, | ||
stateSync *contractsapi.StateSyncedEvent) error { | ||
if stateSync == nil { | ||
return errors.New("no state sync event") | ||
} | ||
|
||
if stateSync.ID.Uint64() < cm.Message.StartID.Uint64() || | ||
stateSync.ID.Uint64() > cm.Message.EndID.Uint64() { | ||
return errors.New("invalid state sync ID") | ||
} | ||
|
||
hash, err := stateSync.EncodeAbi() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return merkle.VerifyProof(stateSync.ID.Uint64()-cm.Message.StartID.Uint64(), | ||
hash, proof, cm.Message.Root) | ||
} | ||
|
||
// ContainsStateSync checks if commitment contains given state sync event | ||
func (cm *CommitmentMessageSigned) ContainsStateSync(stateSyncID uint64) bool { | ||
return cm.Message.StartID.Uint64() <= stateSyncID && cm.Message.EndID.Uint64() >= stateSyncID | ||
} | ||
|
||
// EncodeAbi contains logic for encoding arbitrary data into ABI format | ||
func (cm *CommitmentMessageSigned) EncodeAbi() ([]byte, error) { | ||
blsVerificationPart, err := precompiled.BlsVerificationABIType.Encode( | ||
[2]interface{}{cm.PublicKeys, cm.AggSignature.Bitmap}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
commit := &contractsapi.CommitStateReceiverFn{ | ||
Commitment: cm.Message, | ||
Signature: cm.AggSignature.AggregatedSignature, | ||
Bitmap: blsVerificationPart, | ||
} | ||
|
||
return commit.EncodeAbi() | ||
} | ||
|
||
// DecodeAbi contains logic for decoding given ABI data | ||
func (cm *CommitmentMessageSigned) DecodeAbi(txData []byte) error { | ||
if len(txData) < abiMethodIDLength { | ||
return fmt.Errorf("invalid commitment data, len = %d", len(txData)) | ||
} | ||
|
||
commit := contractsapi.CommitStateReceiverFn{} | ||
|
||
err := commit.DecodeAbi(txData) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
decoded, err := precompiled.BlsVerificationABIType.Decode(commit.Bitmap) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
blsMap, isOk := decoded.(map[string]interface{}) | ||
if !isOk { | ||
return fmt.Errorf("invalid commitment data. Bls verification part not in correct format") | ||
} | ||
|
||
publicKeys, isOk := blsMap["0"].([][]byte) | ||
if !isOk { | ||
return fmt.Errorf("invalid commitment data. Could not find public keys part") | ||
} | ||
|
||
bitmap, isOk := blsMap["1"].([]byte) | ||
if !isOk { | ||
return fmt.Errorf("invalid commitment data. Could not find bitmap part") | ||
} | ||
|
||
*cm = CommitmentMessageSigned{ | ||
Message: commit.Commitment, | ||
AggSignature: Signature{ | ||
AggregatedSignature: commit.Signature, | ||
Bitmap: bitmap, | ||
}, | ||
PublicKeys: publicKeys, | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// getCommitmentMessageSignedTx returns a CommitmentMessageSigned object from a commit state transaction | ||
func getCommitmentMessageSignedTx(txs []*types.Transaction) (*CommitmentMessageSigned, error) { | ||
var commitFn contractsapi.CommitStateReceiverFn | ||
for _, tx := range txs { | ||
// skip non state CommitmentMessageSigned transactions | ||
if tx.Type != types.StateTx || | ||
len(tx.Input) < abiMethodIDLength || | ||
!bytes.Equal(tx.Input[:abiMethodIDLength], commitFn.Sig()) { | ||
continue | ||
} | ||
|
||
obj := &CommitmentMessageSigned{} | ||
|
||
if err := obj.DecodeAbi(tx.Input); err != nil { | ||
return nil, fmt.Errorf("get commitment message signed tx error: %w", err) | ||
} | ||
|
||
return obj, nil | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
// createMerkleTree creates a merkle tree from provided state sync events | ||
// if only one state sync event is provided, a second, empty leaf will be added to merkle tree | ||
// so that we can have a commitment with a single state sync event | ||
func createMerkleTree(stateSyncEvents []*contractsapi.StateSyncedEvent) (*merkle.MerkleTree, error) { | ||
stateSyncData := make([][]byte, len(stateSyncEvents)) | ||
|
||
for i, sse := range stateSyncEvents { | ||
data, err := sse.EncodeAbi() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
stateSyncData[i] = data | ||
} | ||
|
||
return merkle.NewMerkleTree(stateSyncData) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package polybft | ||
|
||
import ( | ||
"math/big" | ||
"testing" | ||
|
||
"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" | ||
"github.com/0xPolygon/polygon-edge/types" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestCommitmentMessage_Hash(t *testing.T) { | ||
t.Parallel() | ||
|
||
const ( | ||
eventsCount = 10 | ||
) | ||
|
||
stateSyncEvents := generateStateSyncEvents(t, eventsCount, 0) | ||
|
||
trie1, err := createMerkleTree(stateSyncEvents) | ||
require.NoError(t, err) | ||
|
||
trie2, err := createMerkleTree(stateSyncEvents[0 : len(stateSyncEvents)-1]) | ||
require.NoError(t, err) | ||
|
||
commitmentMessage1 := newTestCommitmentSigned(t, trie1.Hash(), 2, 8) | ||
commitmentMessage2 := newTestCommitmentSigned(t, trie1.Hash(), 2, 8) | ||
commitmentMessage3 := newTestCommitmentSigned(t, trie1.Hash(), 6, 10) | ||
commitmentMessage4 := newTestCommitmentSigned(t, trie2.Hash(), 2, 8) | ||
|
||
hash1, err := commitmentMessage1.Hash() | ||
require.NoError(t, err) | ||
hash2, err := commitmentMessage2.Hash() | ||
require.NoError(t, err) | ||
hash3, err := commitmentMessage3.Hash() | ||
require.NoError(t, err) | ||
hash4, err := commitmentMessage4.Hash() | ||
require.NoError(t, err) | ||
|
||
require.Equal(t, hash1, hash2) | ||
require.NotEqual(t, hash1, hash3) | ||
require.NotEqual(t, hash1, hash4) | ||
require.NotEqual(t, hash3, hash4) | ||
} | ||
|
||
func TestCommitmentMessage_ToRegisterCommitmentInputData(t *testing.T) { | ||
t.Parallel() | ||
|
||
const epoch, eventsCount = uint64(100), 11 | ||
pendingCommitment, _, _ := buildCommitmentAndStateSyncs(t, eventsCount, epoch, uint64(2)) | ||
expectedSignedCommitmentMsg := &CommitmentMessageSigned{ | ||
Message: pendingCommitment.StateSyncCommitment, | ||
AggSignature: Signature{ | ||
Bitmap: []byte{5, 1}, | ||
AggregatedSignature: []byte{1, 1}, | ||
}, | ||
PublicKeys: [][]byte{{0, 1}, {2, 3}, {4, 5}}, | ||
} | ||
inputData, err := expectedSignedCommitmentMsg.EncodeAbi() | ||
require.NoError(t, err) | ||
require.NotEmpty(t, inputData) | ||
|
||
var actualSignedCommitmentMsg CommitmentMessageSigned | ||
|
||
require.NoError(t, actualSignedCommitmentMsg.DecodeAbi(inputData)) | ||
require.NoError(t, err) | ||
require.Equal(t, *expectedSignedCommitmentMsg.Message, *actualSignedCommitmentMsg.Message) | ||
require.Equal(t, expectedSignedCommitmentMsg.AggSignature, actualSignedCommitmentMsg.AggSignature) | ||
} | ||
|
||
func TestCommitmentMessage_VerifyProof(t *testing.T) { | ||
t.Parallel() | ||
|
||
const epoch, eventsCount = uint64(100), 11 | ||
commitment, commitmentSigned, stateSyncs := buildCommitmentAndStateSyncs(t, eventsCount, epoch, 0) | ||
require.Equal(t, uint64(10), commitment.EndID.Sub(commitment.EndID, commitment.StartID).Uint64()) | ||
|
||
for _, stateSync := range stateSyncs { | ||
leaf, err := stateSync.EncodeAbi() | ||
require.NoError(t, err) | ||
|
||
proof, err := commitment.MerkleTree.GenerateProof(leaf) | ||
require.NoError(t, err) | ||
|
||
execute := &contractsapi.ExecuteStateReceiverFn{ | ||
Proof: proof, | ||
Obj: (*contractsapi.StateSync)(stateSync), | ||
} | ||
|
||
inputData, err := execute.EncodeAbi() | ||
require.NoError(t, err) | ||
|
||
executionStateSync := &contractsapi.ExecuteStateReceiverFn{} | ||
require.NoError(t, executionStateSync.DecodeAbi(inputData)) | ||
require.Equal(t, stateSync.ID.Uint64(), executionStateSync.Obj.ID.Uint64()) | ||
require.Equal(t, stateSync.Sender, executionStateSync.Obj.Sender) | ||
require.Equal(t, stateSync.Receiver, executionStateSync.Obj.Receiver) | ||
require.Equal(t, stateSync.Data, executionStateSync.Obj.Data) | ||
require.Equal(t, proof, executionStateSync.Proof) | ||
|
||
err = commitmentSigned.VerifyStateSyncProof(executionStateSync.Proof, | ||
(*contractsapi.StateSyncedEvent)(executionStateSync.Obj)) | ||
require.NoError(t, err) | ||
} | ||
} | ||
|
||
func TestCommitmentMessage_VerifyProof_NoStateSyncsInCommitment(t *testing.T) { | ||
t.Parallel() | ||
|
||
commitment := &CommitmentMessageSigned{Message: &contractsapi.StateSyncCommitment{StartID: big.NewInt(1), EndID: big.NewInt(10)}} | ||
err := commitment.VerifyStateSyncProof([]types.Hash{}, nil) | ||
assert.ErrorContains(t, err, "no state sync event") | ||
} | ||
|
||
func TestCommitmentMessage_VerifyProof_StateSyncHashNotEqualToProof(t *testing.T) { | ||
t.Parallel() | ||
|
||
const ( | ||
fromIndex = 0 | ||
toIndex = 4 | ||
) | ||
|
||
stateSyncs := generateStateSyncEvents(t, 5, 0) | ||
tree, err := createMerkleTree(stateSyncs) | ||
require.NoError(t, err) | ||
|
||
leaf, err := stateSyncs[0].EncodeAbi() | ||
require.NoError(t, err) | ||
|
||
proof, err := tree.GenerateProof(leaf) | ||
require.NoError(t, err) | ||
|
||
commitment := &CommitmentMessageSigned{ | ||
Message: &contractsapi.StateSyncCommitment{ | ||
StartID: big.NewInt(fromIndex), | ||
EndID: big.NewInt(toIndex), | ||
Root: tree.Hash(), | ||
}, | ||
} | ||
|
||
assert.ErrorContains(t, commitment.VerifyStateSyncProof(proof, stateSyncs[4]), "not a member of merkle tree") | ||
} | ||
|
||
func newTestCommitmentSigned(t *testing.T, root types.Hash, startID, endID int64) *CommitmentMessageSigned { | ||
t.Helper() | ||
|
||
return &CommitmentMessageSigned{ | ||
Message: &contractsapi.StateSyncCommitment{ | ||
StartID: big.NewInt(startID), | ||
EndID: big.NewInt(endID), | ||
Root: root, | ||
}, | ||
AggSignature: Signature{}, | ||
PublicKeys: [][]byte{}, | ||
} | ||
} | ||
|
||
func buildCommitmentAndStateSyncs(t *testing.T, stateSyncsCount int, | ||
epoch, startIdx uint64) (*PendingCommitment, *CommitmentMessageSigned, []*contractsapi.StateSyncedEvent) { | ||
t.Helper() | ||
|
||
stateSyncEvents := generateStateSyncEvents(t, stateSyncsCount, startIdx) | ||
commitment, err := NewPendingCommitment(epoch, stateSyncEvents) | ||
require.NoError(t, err) | ||
|
||
commitmentSigned := &CommitmentMessageSigned{ | ||
Message: commitment.StateSyncCommitment, | ||
AggSignature: Signature{ | ||
AggregatedSignature: []byte{}, | ||
Bitmap: []byte{}, | ||
}, | ||
PublicKeys: [][]byte{}, | ||
} | ||
|
||
return commitment, commitmentSigned, stateSyncEvents | ||
} |
Oops, something went wrong.