From fd43798c28f24b4de34da33bb6bc7b01608d1dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Sat, 6 May 2023 08:22:44 +0200 Subject: [PATCH 1/9] Allow min commitment of single state sync event --- consensus/polybft/state_sync_manager.go | 17 +++++++---------- consensus/polybft/state_transaction.go | 11 ++++++++--- e2e-polybft/e2e/bridge_test.go | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/consensus/polybft/state_sync_manager.go b/consensus/polybft/state_sync_manager.go index 2ea36e064e..499d97cb41 100644 --- a/consensus/polybft/state_sync_manager.go +++ b/consensus/polybft/state_sync_manager.go @@ -503,17 +503,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 +520,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 +542,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 +555,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_transaction.go b/consensus/polybft/state_transaction.go index ab0706b2d9..c8c602cc80 100644 --- a/consensus/polybft/state_transaction.go +++ b/consensus/polybft/state_transaction.go @@ -218,7 +218,7 @@ func getCommitmentMessageSignedTx(txs []*types.Transaction) (*CommitmentMessageS } func createMerkleTree(stateSyncEvents []*contractsapi.StateSyncedEvent) (*merkle.MerkleTree, error) { - ssh := make([][]byte, len(stateSyncEvents)) + stateSyncData := make([][]byte, len(stateSyncEvents)) for i, sse := range stateSyncEvents { data, err := sse.EncodeAbi() @@ -226,8 +226,13 @@ func createMerkleTree(stateSyncEvents []*contractsapi.StateSyncedEvent) (*merkle return nil, err } - ssh[i] = data + stateSyncData[i] = data } - return merkle.NewMerkleTree(ssh) + if len(stateSyncEvents) == 1 { + //nolint:makezero + stateSyncData = append(stateSyncData, nil) + } + + return merkle.NewMerkleTree(stateSyncData) } diff --git a/e2e-polybft/e2e/bridge_test.go b/e2e-polybft/e2e/bridge_test.go index 3483f43b5b..f6da998136 100644 --- a/e2e-polybft/e2e/bridge_test.go +++ b/e2e-polybft/e2e/bridge_test.go @@ -184,7 +184,7 @@ func TestE2E_Bridge_Transfers(t *testing.T) { t.Run("multiple deposit batches per epoch", func(t *testing.T) { const ( - depositsSubset = 2 + depositsSubset = 1 ) initialBlockNum, err := childEthEndpoint.BlockNumber() From 04b4d0a0b4d870ed0c005eb44135aa2be7bff08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Sat, 6 May 2023 10:03:52 +0200 Subject: [PATCH 2/9] Fix assertion in checkStateSyncResultLogs --- e2e-polybft/e2e/helpers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From 44591e9cace64b0169ede1cf6198940ec623c4ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Sat, 6 May 2023 10:36:19 +0200 Subject: [PATCH 3/9] Change assertions so that multiple deposit batches test can be run independently --- e2e-polybft/e2e/bridge_test.go | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/e2e-polybft/e2e/bridge_test.go b/e2e-polybft/e2e/bridge_test.go index f6da998136..5f6e02951f 100644 --- a/e2e-polybft/e2e/bridge_test.go +++ b/e2e-polybft/e2e/bridge_test.go @@ -187,6 +187,20 @@ func TestE2E_Bridge_Transfers(t *testing.T) { 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, uint64(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 From 527ba8aebc1dae5f8e340a807334675dcfa63109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= Date: Sun, 7 May 2023 07:20:01 +0200 Subject: [PATCH 4/9] Remove unnecesary cast --- consensus/polybft/state_transaction.go | 2 +- e2e-polybft/e2e/bridge_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/polybft/state_transaction.go b/consensus/polybft/state_transaction.go index c8c602cc80..f32006592e 100644 --- a/consensus/polybft/state_transaction.go +++ b/consensus/polybft/state_transaction.go @@ -231,7 +231,7 @@ func createMerkleTree(stateSyncEvents []*contractsapi.StateSyncedEvent) (*merkle if len(stateSyncEvents) == 1 { //nolint:makezero - stateSyncData = append(stateSyncData, nil) + stateSyncData = append(stateSyncData, []byte{}) } return merkle.NewMerkleTree(stateSyncData) diff --git a/e2e-polybft/e2e/bridge_test.go b/e2e-polybft/e2e/bridge_test.go index 5f6e02951f..661fb97c25 100644 --- a/e2e-polybft/e2e/bridge_test.go +++ b/e2e-polybft/e2e/bridge_test.go @@ -233,7 +233,7 @@ func TestE2E_Bridge_Transfers(t *testing.T) { lastCommittedID, err := types.ParseUint64orHex(&commitmentIDRaw) require.NoError(t, err) - require.Equal(t, uint64(initialCommittedID+depositsSubset), lastCommittedID) + require.Equal(t, initialCommittedID+depositsSubset, lastCommittedID) // send some more transactions to the bridge to build another commitment in epoch require.NoError( From 000f432bae5047e69d9daeb91165df0504dcea3f Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Mon, 15 May 2023 15:15:09 +0200 Subject: [PATCH 5/9] Add UT --- merkle-tree/merkle_tree_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/merkle-tree/merkle_tree_test.go b/merkle-tree/merkle_tree_test.go index 2f342a9277..426014c5e0 100644 --- a/merkle-tree/merkle_tree_test.go +++ b/merkle-tree/merkle_tree_test.go @@ -90,3 +90,22 @@ func TestMerkleTree_VerifyProof_TreeWithOneNode(t *testing.T) { // empty leaf require.ErrorContains(t, VerifyProof(11, []byte{}, proof, tree.Hash()), "empty leaf") } + +func TestMerkleTree_WithOneLeaf_AndOneEmptyLeaf(t *testing.T) { + t.Parallel() + + leafData := []byte{1} + treeData := [][]byte{leafData, {}} // with one empty leaf + + tree, err := NewMerkleTree(treeData) + require.NoError(t, err) + + proof, err := tree.GenerateProof(leafData) + require.NoError(t, err) + require.NotEmpty(t, proof) + + index, err := tree.LeafIndex(leafData) + require.NoError(t, err) + require.Equal(t, uint64(0), index) // should be 0 + require.NoError(t, VerifyProof(index, leafData, proof, tree.Hash())) +} From 6b229aada054edd932f55822bf32e98cdf75687a Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 16 May 2023 14:05:35 +0200 Subject: [PATCH 6/9] Reorganize files --- consensus/polybft/state_sync_commitment.go | 207 ++++++++++++++++++ .../polybft/state_sync_commitment_test.go | 178 +++++++++++++++ consensus/polybft/state_transaction.go | 196 +---------------- consensus/polybft/state_transaction_test.go | 165 -------------- 4 files changed, 386 insertions(+), 360 deletions(-) create mode 100644 consensus/polybft/state_sync_commitment.go create mode 100644 consensus/polybft/state_sync_commitment_test.go diff --git a/consensus/polybft/state_sync_commitment.go b/consensus/polybft/state_sync_commitment.go new file mode 100644 index 0000000000..8aeeb45f44 --- /dev/null +++ b/consensus/polybft/state_sync_commitment.go @@ -0,0 +1,207 @@ +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 + } + + if len(stateSyncEvents) == 1 { + //nolint:makezero + stateSyncData = append(stateSyncData, []byte{}) + } + + 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_transaction.go b/consensus/polybft/state_transaction.go index f32006592e..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,45 +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) { - stateSyncData := make([][]byte, len(stateSyncEvents)) - - for i, sse := range stateSyncEvents { - data, err := sse.EncodeAbi() - if err != nil { - return nil, err - } - - stateSyncData[i] = data - } - - if len(stateSyncEvents) == 1 { - //nolint:makezero - stateSyncData = append(stateSyncData, []byte{}) - } - - return merkle.NewMerkleTree(stateSyncData) -} 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() From 64ea2f442798b2c6b1bd6fcd217939e6bcae4f2b Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 16 May 2023 14:40:37 +0200 Subject: [PATCH 7/9] Fix UT --- consensus/polybft/state_sync_manager_test.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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) { From 5df50168bb1bf54486d2b28c58cfefc5153c355f Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 16 May 2023 14:50:35 +0200 Subject: [PATCH 8/9] Remove constant --- consensus/polybft/state_sync_manager.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/consensus/polybft/state_sync_manager.go b/consensus/polybft/state_sync_manager.go index 499d97cb41..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 From 1046cf43f5422fc7b1fbf1ed75403b86accaa0c1 Mon Sep 17 00:00:00 2001 From: Goran Rojovic Date: Tue, 23 May 2023 11:52:14 +0200 Subject: [PATCH 9/9] Fix --- consensus/polybft/state_sync_commitment.go | 5 ----- merkle-tree/merkle_tree_test.go | 19 ------------------- 2 files changed, 24 deletions(-) diff --git a/consensus/polybft/state_sync_commitment.go b/consensus/polybft/state_sync_commitment.go index 8aeeb45f44..6f109d834f 100644 --- a/consensus/polybft/state_sync_commitment.go +++ b/consensus/polybft/state_sync_commitment.go @@ -198,10 +198,5 @@ func createMerkleTree(stateSyncEvents []*contractsapi.StateSyncedEvent) (*merkle stateSyncData[i] = data } - if len(stateSyncEvents) == 1 { - //nolint:makezero - stateSyncData = append(stateSyncData, []byte{}) - } - return merkle.NewMerkleTree(stateSyncData) } diff --git a/merkle-tree/merkle_tree_test.go b/merkle-tree/merkle_tree_test.go index 426014c5e0..2f342a9277 100644 --- a/merkle-tree/merkle_tree_test.go +++ b/merkle-tree/merkle_tree_test.go @@ -90,22 +90,3 @@ func TestMerkleTree_VerifyProof_TreeWithOneNode(t *testing.T) { // empty leaf require.ErrorContains(t, VerifyProof(11, []byte{}, proof, tree.Hash()), "empty leaf") } - -func TestMerkleTree_WithOneLeaf_AndOneEmptyLeaf(t *testing.T) { - t.Parallel() - - leafData := []byte{1} - treeData := [][]byte{leafData, {}} // with one empty leaf - - tree, err := NewMerkleTree(treeData) - require.NoError(t, err) - - proof, err := tree.GenerateProof(leafData) - require.NoError(t, err) - require.NotEmpty(t, proof) - - index, err := tree.LeafIndex(leafData) - require.NoError(t, err) - require.Equal(t, uint64(0), index) // should be 0 - require.NoError(t, VerifyProof(index, leafData, proof, tree.Hash())) -}