diff --git a/consensus/polybft/state_sync_commitment.go b/consensus/polybft/state_sync_commitment.go new file mode 100644 index 0000000000..6f109d834f --- /dev/null +++ b/consensus/polybft/state_sync_commitment.go @@ -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) +} diff --git a/consensus/polybft/state_sync_commitment_test.go b/consensus/polybft/state_sync_commitment_test.go new file mode 100644 index 0000000000..61018f7517 --- /dev/null +++ b/consensus/polybft/state_sync_commitment_test.go @@ -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 +} diff --git a/consensus/polybft/state_sync_manager.go b/consensus/polybft/state_sync_manager.go index 2ea36e064e..e6383771f1 100644 --- a/consensus/polybft/state_sync_manager.go +++ b/consensus/polybft/state_sync_manager.go @@ -23,12 +23,6 @@ import ( "google.golang.org/protobuf/proto" ) -const ( - // minimum number of stateSyncEvents that a commitment can have - // (minimum number is 2 because smart contract expects that the merkle tree has at least two leaves) - minCommitmentSize = 2 -) - type StateSyncProof struct { Proof []types.Hash StateSync *contractsapi.StateSyncedEvent @@ -503,17 +497,14 @@ func (s *stateSyncManager) buildCommitment() error { s.lock.Lock() defer s.lock.Unlock() - epoch := s.epoch - fromIndex := s.nextCommittedIndex - - stateSyncEvents, err := s.state.StateSyncStore.getStateSyncEventsForCommitment(fromIndex, - fromIndex+s.config.maxCommitmentSize-1) + stateSyncEvents, err := s.state.StateSyncStore.getStateSyncEventsForCommitment(s.nextCommittedIndex, + s.nextCommittedIndex+s.config.maxCommitmentSize-1) if err != nil && !errors.Is(err, errNotEnoughStateSyncs) { return fmt.Errorf("failed to get state sync events for commitment. Error: %w", err) } - if len(stateSyncEvents) < minCommitmentSize { - // there is not enough state sync events to build at least the minimum commitment + if len(stateSyncEvents) == 0 { + // there are no state sync events return nil } @@ -523,7 +514,7 @@ func (s *stateSyncManager) buildCommitment() error { return nil } - commitment, err := NewPendingCommitment(epoch, stateSyncEvents) + commitment, err := NewPendingCommitment(s.epoch, stateSyncEvents) if err != nil { return err } @@ -545,7 +536,7 @@ func (s *stateSyncManager) buildCommitment() error { Signature: signature, } - if _, err = s.state.StateSyncStore.insertMessageVote(epoch, hashBytes, sig); err != nil { + if _, err = s.state.StateSyncStore.insertMessageVote(s.epoch, hashBytes, sig); err != nil { return fmt.Errorf( "failed to insert signature for hash=%v to the state. Error: %w", hex.EncodeToString(hashBytes), @@ -558,7 +549,7 @@ func (s *stateSyncManager) buildCommitment() error { Hash: hashBytes, Signature: signature, From: s.config.key.String(), - EpochNumber: epoch, + EpochNumber: s.epoch, }) s.logger.Debug( diff --git a/consensus/polybft/state_sync_manager_test.go b/consensus/polybft/state_sync_manager_test.go index 4deba37f60..f60a1408eb 100644 --- a/consensus/polybft/state_sync_manager_test.go +++ b/consensus/polybft/state_sync_manager_test.go @@ -372,16 +372,18 @@ func TestStateSyncerManager_AddLog_BuildCommitments(t *testing.T) { stateSyncs, err = s.state.StateSyncStore.getStateSyncEventsForCommitment(0, 0) require.NoError(t, err) require.Len(t, stateSyncs, 1) - require.Len(t, s.pendingCommitments, 0) + require.Len(t, s.pendingCommitments, 1) + require.Equal(t, uint64(0), s.pendingCommitments[0].StartID.Uint64()) + require.Equal(t, uint64(0), s.pendingCommitments[0].EndID.Uint64()) // add one more log to have a minimum commitment goodLog2 := goodLog.Copy() goodLog2.Topics[1] = ethgo.BytesToHash([]byte{0x1}) // state sync index 1 s.AddLog(goodLog2) - require.Len(t, s.pendingCommitments, 1) - require.Equal(t, uint64(0), s.pendingCommitments[0].StartID.Uint64()) - require.Equal(t, uint64(1), s.pendingCommitments[0].EndID.Uint64()) + require.Len(t, s.pendingCommitments, 2) + require.Equal(t, uint64(0), s.pendingCommitments[1].StartID.Uint64()) + require.Equal(t, uint64(1), s.pendingCommitments[1].EndID.Uint64()) // add two more logs to have larger commitments goodLog3 := goodLog.Copy() @@ -392,9 +394,9 @@ func TestStateSyncerManager_AddLog_BuildCommitments(t *testing.T) { goodLog4.Topics[1] = ethgo.BytesToHash([]byte{0x3}) // state sync index 3 s.AddLog(goodLog4) - require.Len(t, s.pendingCommitments, 3) - require.Equal(t, uint64(0), s.pendingCommitments[2].StartID.Uint64()) - require.Equal(t, uint64(3), s.pendingCommitments[2].EndID.Uint64()) + require.Len(t, s.pendingCommitments, 4) + require.Equal(t, uint64(0), s.pendingCommitments[3].StartID.Uint64()) + require.Equal(t, uint64(3), s.pendingCommitments[3].EndID.Uint64()) } func TestStateSyncerManager_EventTracker_Sync(t *testing.T) { diff --git a/consensus/polybft/state_transaction.go b/consensus/polybft/state_transaction.go index ab0706b2d9..0d98d3faf4 100644 --- a/consensus/polybft/state_transaction.go +++ b/consensus/polybft/state_transaction.go @@ -2,164 +2,12 @@ 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 ( - abiMethodIDLength = 4 - 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 -} +const abiMethodIDLength = 4 func decodeStateTransaction(txData []byte) (contractsapi.StateTransactionInput, error) { if len(txData) < abiMethodIDLength { @@ -194,40 +42,3 @@ func decodeStateTransaction(txData []byte) (contractsapi.StateTransactionInput, return obj, nil } - -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 -} - -func createMerkleTree(stateSyncEvents []*contractsapi.StateSyncedEvent) (*merkle.MerkleTree, error) { - ssh := make([][]byte, len(stateSyncEvents)) - - for i, sse := range stateSyncEvents { - data, err := sse.EncodeAbi() - if err != nil { - return nil, err - } - - ssh[i] = data - } - - return merkle.NewMerkleTree(ssh) -} diff --git a/consensus/polybft/state_transaction_test.go b/consensus/polybft/state_transaction_test.go index 51512ebd60..78129342e9 100644 --- a/consensus/polybft/state_transaction_test.go +++ b/consensus/polybft/state_transaction_test.go @@ -13,171 +13,6 @@ import ( "github.com/umbracle/ethgo/abi" ) -func newTestCommitmentSigned(root types.Hash, startID, endID int64) *CommitmentMessageSigned { - return &CommitmentMessageSigned{ - Message: &contractsapi.StateSyncCommitment{ - StartID: big.NewInt(startID), - EndID: big.NewInt(endID), - Root: root, - }, - AggSignature: Signature{}, - PublicKeys: [][]byte{}, - } -} - -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(trie1.Hash(), 2, 8) - commitmentMessage2 := newTestCommitmentSigned(trie1.Hash(), 2, 8) - commitmentMessage3 := newTestCommitmentSigned(trie1.Hash(), 6, 10) - commitmentMessage4 := newTestCommitmentSigned(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 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 -} - func TestStateTransaction_Signature(t *testing.T) { t.Parallel() diff --git a/e2e-polybft/e2e/bridge_test.go b/e2e-polybft/e2e/bridge_test.go index 3483f43b5b..661fb97c25 100644 --- a/e2e-polybft/e2e/bridge_test.go +++ b/e2e-polybft/e2e/bridge_test.go @@ -184,9 +184,23 @@ func TestE2E_Bridge_Transfers(t *testing.T) { t.Run("multiple deposit batches per epoch", func(t *testing.T) { const ( - depositsSubset = 2 + depositsSubset = 1 ) + txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(validatorSrv.JSONRPC())) + require.NoError(t, err) + + lastCommittedIDMethod := contractsapi.StateReceiver.Abi.GetMethod("lastCommittedId") + lastCommittedIDInput, err := lastCommittedIDMethod.Encode([]interface{}{}) + require.NoError(t, err) + + // check that we submitted the minimal commitment to smart contract + commitmentIDRaw, err := txRelayer.Call(ethgo.ZeroAddress, ethgo.Address(contracts.StateReceiverContract), lastCommittedIDInput) + require.NoError(t, err) + + initialCommittedID, err := types.ParseUint64orHex(&commitmentIDRaw) + require.NoError(t, err) + initialBlockNum, err := childEthEndpoint.BlockNumber() require.NoError(t, err) @@ -212,20 +226,14 @@ func TestE2E_Bridge_Transfers(t *testing.T) { midBlockNumber := initialBlockNum + 2*sprintSize require.NoError(t, cluster.WaitForBlock(midBlockNumber, 2*time.Minute)) - txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithClient(validatorSrv.JSONRPC())) - require.NoError(t, err) - - lastCommittedIDMethod := contractsapi.StateReceiver.Abi.GetMethod("lastCommittedId") - encode, err := lastCommittedIDMethod.Encode([]interface{}{}) - require.NoError(t, err) - // check that we submitted the minimal commitment to smart contract - commitmentIDRaw, err := txRelayer.Call(ethgo.ZeroAddress, ethgo.Address(contracts.StateReceiverContract), encode) + commitmentIDRaw, err = txRelayer.Call(ethgo.ZeroAddress, + ethgo.Address(contracts.StateReceiverContract), lastCommittedIDInput) require.NoError(t, err) lastCommittedID, err := types.ParseUint64orHex(&commitmentIDRaw) require.NoError(t, err) - require.Equal(t, uint64(transfersCount+depositsSubset), lastCommittedID) + require.Equal(t, initialCommittedID+depositsSubset, lastCommittedID) // send some more transactions to the bridge to build another commitment in epoch require.NoError( @@ -245,13 +253,13 @@ func TestE2E_Bridge_Transfers(t *testing.T) { require.NoError(t, cluster.WaitForBlock(midBlockNumber+5*sprintSize, 3*time.Minute)) // check that we submitted the minimal commitment to smart contract - commitmentIDRaw, err = txRelayer.Call(ethgo.ZeroAddress, ethgo.Address(contracts.StateReceiverContract), encode) + commitmentIDRaw, err = txRelayer.Call(ethgo.ZeroAddress, ethgo.Address(contracts.StateReceiverContract), lastCommittedIDInput) require.NoError(t, err) // check that the second (larger commitment) was also submitted in epoch lastCommittedID, err = types.ParseUint64orHex(&commitmentIDRaw) require.NoError(t, err) - require.Equal(t, uint64(2*transfersCount), lastCommittedID) + require.Equal(t, initialCommittedID+uint64(transfersCount), lastCommittedID) // the transactions are mined and state syncs should be executed by the relayer // and there should be a success events diff --git a/e2e-polybft/e2e/helpers_test.go b/e2e-polybft/e2e/helpers_test.go index ac53c2bc21..d4e2360a37 100644 --- a/e2e-polybft/e2e/helpers_test.go +++ b/e2e-polybft/e2e/helpers_test.go @@ -228,7 +228,7 @@ func checkStateSyncResultLogs( expectedCount int, ) { t.Helper() - require.Equal(t, len(logs), expectedCount) + require.Equal(t, expectedCount, len(logs)) var stateSyncResultEvent contractsapi.StateSyncResultEvent for _, log := range logs {