diff --git a/bridge/elrond/client.go b/bridge/elrond/client.go index 3885d832..306e860a 100644 --- a/bridge/elrond/client.go +++ b/bridge/elrond/client.go @@ -135,8 +135,7 @@ func (c *client) GetPending(_ context.Context) (*bridge.Batch, error) { var transactions []*bridge.DepositTransaction for i := 1; i < len(responseData); i += numArgs { if len(responseData) < i+idxAmount { - c.log.Warn("Elrond: got an unexpected number of arguments", "index", i, "total args", len(responseData)) - break + return nil, fmt.Errorf("Elrond: got an unexpected number of arguments, index %d, total args: %d", i, len(responseData)) } amount := new(big.Int).SetBytes(responseData[i+idxAmount]) @@ -157,10 +156,9 @@ func (c *client) GetPending(_ context.Context) (*bridge.Batch, error) { Amount: amount, DepositNonce: bridge.NewNonce(depositNonce), BlockNonce: bridge.NewNonce(blockNonce), - Status: 0, Error: nil, } - c.log.Trace("created deposit transaction: " + tx.String()) + c.log.Debug("Elrond: created deposit transaction: " + tx.String()) transactions = append(transactions, tx) } @@ -169,9 +167,12 @@ func (c *client) GetPending(_ context.Context) (*bridge.Batch, error) { return nil, fmt.Errorf("%w in client.GetPending, parseIntFromByteSlice(responseData[0])", err) } + c.log.Debug("Elrond: created batch", "batchID", batchId, "num transactions", len(transactions)) + return &bridge.Batch{ Id: bridge.NewBatchId(batchId), Transactions: transactions, + Statuses: make([]byte, len(transactions)), }, nil } @@ -189,18 +190,26 @@ func parseIntFromByteSlice(buff []byte) (int64, error) { } // ProposeSetStatus will trigger the proposal of the ESDT safe set current transaction batch status operation -func (c *client) ProposeSetStatus(_ context.Context, batch *bridge.Batch) { +func (c *client) ProposeSetStatus(ctx context.Context, batch *bridge.Batch) { builder := newBuilder(c.log). Func("proposeEsdtSafeSetCurrentTransactionBatchStatus"). BatchId(batch.Id) - for _, tx := range batch.Transactions { - builder = builder.Int(big.NewInt(int64(tx.Status))) + newBatch, err := c.GetPending(ctx) + if err != nil { + c.log.Error("Elrond: get pending batch failed in ProposeSetStatus", "error", err) + return + } + + batch.ResolveNewDeposits(len(newBatch.Statuses)) + + for _, stat := range batch.Statuses { + builder = builder.Int(big.NewInt(int64(stat))) } hash, err := c.sendTransaction(builder, c.gasMapConfig.ProposeStatus) if err != nil { - c.log.Error("Elrond: send transaction failed", "error", err.Error()) + c.log.Error("Elrond: send transaction failed", "error", err) return } @@ -220,13 +229,17 @@ func (c *client) ProposeTransfer(_ context.Context, batch *bridge.Batch) (string BigInt(tx.Amount) } + batchData, errMarshal := json.Marshal(batch) + if errMarshal != nil { + c.log.Warn("Elrond: error not critical while serializing transaction", "error", errMarshal) + } + gasLimit := c.gasMapConfig.ProposeTransferBase + uint64(len(batch.Transactions))*c.gasMapConfig.ProposeTransferForEach hash, err := c.sendTransaction(builder, gasLimit) - if err == nil { - c.log.Info("Elrond: Proposed transfer for batch ", batch.Id, " with hash ", hash) + c.log.Info("Elrond: Proposed transfer for batch ", batch.Id, "with hash", hash, "batch data", string(batchData)) } else { - c.log.Error("Elrond: Propose transfer errored", "error", err.Error()) + c.log.Error("Elrond: Propose transfer errored", "batch data", string(batchData), "error", err) } return hash, err @@ -265,13 +278,20 @@ func (c *client) GetActionIdForProposeTransfer(_ context.Context, batch *bridge. } // WasProposedSetStatus returns true if the proposed set status was triggered -func (c *client) WasProposedSetStatus(_ context.Context, batch *bridge.Batch) bool { +func (c *client) WasProposedSetStatus(ctx context.Context, batch *bridge.Batch) bool { valueRequest := newValueBuilder(c.bridgeAddress, c.address.AddressAsBech32String(), c.log). Func("wasSetCurrentTransactionBatchStatusActionProposed"). BatchId(batch.Id) - for _, tx := range batch.Transactions { - valueRequest = valueRequest.BigInt(big.NewInt(int64(tx.Status))) + newBatch, err := c.GetPending(ctx) + if err != nil { + c.log.Error("Elrond: get pending batch failed in WasProposedSetStatus", "error", err) + return false + } + batch.ResolveNewDeposits(len(newBatch.Statuses)) + + for _, stat := range batch.Statuses { + valueRequest = valueRequest.BigInt(big.NewInt(int64(stat))) } return c.executeBoolQuery(valueRequest.Build()) @@ -312,6 +332,8 @@ func (c *client) GetTransactionsStatuses(_ context.Context, batchId bridge.Batch return nil, fmt.Errorf("%w status is finished, no results are given", ErrMalformedBatchResponse) } + c.log.Debug("Elrond: got transaction status", "batchID", batchId, "status", results) + return results, nil } @@ -324,13 +346,20 @@ func getStatusFromBuff(buff []byte) (byte, error) { } // GetActionIdForSetStatusOnPendingTransfer returns the action ID for setting the status on the pending transfer batch -func (c *client) GetActionIdForSetStatusOnPendingTransfer(_ context.Context, batch *bridge.Batch) bridge.ActionId { +func (c *client) GetActionIdForSetStatusOnPendingTransfer(ctx context.Context, batch *bridge.Batch) bridge.ActionId { valueRequest := newValueBuilder(c.bridgeAddress, c.address.AddressAsBech32String(), c.log). Func("getActionIdForSetCurrentTransactionBatchStatus"). BatchId(batch.Id) - for _, tx := range batch.Transactions { - valueRequest = valueRequest.BigInt(big.NewInt(int64(tx.Status))) + newBatch, err := c.GetPending(ctx) + if err != nil { + c.log.Error("Elrond: get pending batch failed in WasProposedSetStatus", "error", err) + return bridge.NewActionId(0) + } + batch.ResolveNewDeposits(len(newBatch.Statuses)) + + for _, stat := range batch.Statuses { + valueRequest = valueRequest.BigInt(big.NewInt(int64(stat))) } response, err := c.executeUintQuery(valueRequest.Build()) @@ -339,6 +368,8 @@ func (c *client) GetActionIdForSetStatusOnPendingTransfer(_ context.Context, bat return bridge.NewActionId(0) } + c.log.Debug("Elrond: got actionID", "actionID", response) + return bridge.NewActionId(int64(response)) } @@ -359,17 +390,22 @@ func (c *client) WasExecuted(_ context.Context, actionId bridge.ActionId, _ brid } // Sign will trigger the execution of a sign operation -func (c *client) Sign(_ context.Context, actionId bridge.ActionId, _ *bridge.Batch) (string, error) { +func (c *client) Sign(_ context.Context, actionId bridge.ActionId, batch *bridge.Batch) (string, error) { builder := newBuilder(c.log). Func("sign"). ActionId(actionId) hash, err := c.sendTransaction(builder, c.gasMapConfig.Sign) + batchData, err := json.Marshal(batch) + if err != nil { + c.log.Warn("Elrond: error not critical while serializing transaction", "error", err) + } + if err == nil { - c.log.Info("Elrond: Signed", "hash", hash) + c.log.Info("Elrond: Signed", "hash", hash, "batch data", string(batchData)) } else { - c.log.Error("Elrond: Sign failed", "error", err.Error()) + c.log.Error("Elrond: Sign failed", "batch data", string(batchData), "error", err) } return hash, err @@ -384,17 +420,26 @@ func (c *client) Execute(_ context.Context, actionId bridge.ActionId, batch *bri gasLimit := c.gasMapConfig.PerformActionBase + uint64(len(batch.Transactions))*c.gasMapConfig.PerformActionForEach hash, err := c.sendTransaction(builder, gasLimit) + batchData, err := json.Marshal(batch) + if err != nil { + c.log.Warn("Elrond: error not critical while serializing transaction", "error", err) + } + if err == nil { - c.log.Info("Elrond: Executed action", "action ID", actionId, "hash", hash) + c.log.Info("Elrond: Executed action", "actionID", actionId, "batch data", string(batchData), "hash", hash) } else { - c.log.Info("Elrond: Execution failed for action", "action ID", actionId, "hash", hash, "error", err.Error()) + c.log.Info("Elrond: Execution failed for action", + "actionID", actionId, + "batch data", string(batchData), + "hash", hash, + "error", err) } return hash, err } // SignersCount returns the signers count -func (c *client) SignersCount(_ *bridge.Batch, actionId bridge.ActionId, _ bridge.SignaturesHolder) uint { +func (c *client) SignersCount(_ context.Context, _ *bridge.Batch, actionId bridge.ActionId, _ bridge.SignaturesHolder) uint { valueRequest := newValueBuilder(c.bridgeAddress, c.address.AddressAsBech32String(), c.log). Func("getActionSignerCount"). ActionId(actionId). @@ -420,6 +465,8 @@ func (c *client) GetTokenId(address string) string { c.log.Error(err.Error()) } + c.log.Debug("Elrond: get token ID", "address", address, "tokenID", tokenId) + return tokenId } @@ -435,6 +482,8 @@ func (c *client) GetErc20Address(tokenId string) string { c.log.Error(err.Error()) } + c.log.Debug("Elrond: get erc20 address", "tokenID", tokenId, "address", address) + return address } diff --git a/bridge/elrond/client_test.go b/bridge/elrond/client_test.go index a90a1878..595c58f8 100644 --- a/bridge/elrond/client_test.go +++ b/bridge/elrond/client_test.go @@ -149,7 +149,6 @@ func TestGetPending(t *testing.T) { Amount: big.NewInt(1), DepositNonce: bridge.NewNonce(1), BlockNonce: bridge.NewNonce(154947), - Status: 0, Error: nil, } tx2 := &bridge.DepositTransaction{ @@ -160,12 +159,12 @@ func TestGetPending(t *testing.T) { Amount: big.NewInt(2), DepositNonce: bridge.NewNonce(2), BlockNonce: bridge.NewNonce(154947), - Status: 0, Error: nil, } expected := &bridge.Batch{ Id: bridge.NewBatchId(1), Transactions: []*bridge.DepositTransaction{tx1, tx2}, + Statuses: make([]byte, 2), } assert.Equal(t, expected, actual) @@ -259,7 +258,7 @@ func TestProposeSetStatus(t *testing.T) { proxy := &testProxy{ transactionCost: 1024, queryResponseCode: "ok", - queryResponseData: [][]byte{}, + queryResponseData: make([][]byte, 13), } c, _ := buildTestClient(proxy) @@ -272,7 +271,6 @@ func TestProposeSetStatus(t *testing.T) { TokenAddress: "0x3a41ed2dD119E44B802c87E84840F7C85206f4f1", Amount: big.NewInt(42), DepositNonce: bridge.NewNonce(1), - Status: bridge.Executed, }, { To: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", @@ -280,9 +278,9 @@ func TestProposeSetStatus(t *testing.T) { TokenAddress: "0x3a41ed2dD119E44B802c87E84840F7C85206f4f1", Amount: big.NewInt(42), DepositNonce: bridge.NewNonce(1), - Status: bridge.Rejected, }, }, + Statuses: []byte{bridge.Executed, bridge.Rejected}, } c.ProposeSetStatus(context.TODO(), batch) @@ -309,7 +307,6 @@ func TestExecute(t *testing.T) { TokenAddress: "0x3a41ed2dD119E44B802c87E84840F7C85206f4f1", Amount: big.NewInt(42), DepositNonce: bridge.NewNonce(1), - Status: bridge.Executed, }, { To: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", @@ -317,9 +314,9 @@ func TestExecute(t *testing.T) { TokenAddress: "0x3a41ed2dD119E44B802c87E84840F7C85206f4f1", Amount: big.NewInt(42), DepositNonce: bridge.NewNonce(1), - Status: bridge.Rejected, }, }, + Statuses: []byte{bridge.Executed, bridge.Rejected}, } hash, _ := c.Execute(context.TODO(), bridge.NewActionId(42), batch, nil) @@ -346,9 +343,9 @@ func TestWasProposedTransfer(t *testing.T) { TokenAddress: "0x3a41ed2dD119E44B802c87E84840F7C85206f4f1", Amount: big.NewInt(42), DepositNonce: bridge.NewNonce(1), - Status: bridge.Executed, }, }, + Statuses: []byte{bridge.Executed}, } got := c.WasProposedTransfer(context.TODO(), batch) @@ -430,7 +427,7 @@ func TestSignersCount(t *testing.T) { proxy := &testProxy{queryResponseCode: "ok", queryResponseData: [][]byte{{byte(42)}}} c, _ := buildTestClient(proxy) - got := c.SignersCount(nil, bridge.NewActionId(0), nil) + got := c.SignersCount(context.TODO(), nil, bridge.NewActionId(0), nil) assert.Equal(t, uint(42), got) } @@ -443,10 +440,9 @@ func TestWasProposedSetStatus(t *testing.T) { batch := &bridge.Batch{ Id: bridge.NewBatchId(1), Transactions: []*bridge.DepositTransaction{ - { - Status: bridge.Rejected, - }, + {}, }, + Statuses: []byte{bridge.Rejected}, } got := c.WasProposedSetStatus(context.TODO(), batch) @@ -455,12 +451,17 @@ func TestWasProposedSetStatus(t *testing.T) { assert.Equal(t, "04", proxy.lastQueryArgs[1]) }) t.Run("will return false when response is empty", func(t *testing.T) { - proxy := &testProxy{queryResponseCode: "ok", queryResponseData: [][]byte{{}}} + proxy := &testProxy{ + queryResponseCode: "ok", + queryResponseData: make([][]byte, 7)} c, _ := buildTestClient(proxy) batch := &bridge.Batch{ - Id: bridge.NewBatchId(0), - Transactions: []*bridge.DepositTransaction{}, + Id: bridge.NewBatchId(0), + Transactions: []*bridge.DepositTransaction{ + {}, + }, + Statuses: make([]byte, 1), } got := c.WasProposedSetStatus(context.TODO(), batch) @@ -513,9 +514,9 @@ func TestGetActionIdForSetStatusOnPendingTransfer(t *testing.T) { TokenAddress: "0x3a41ed2dD119E44B802c87E84840F7C85206f4f1", Amount: big.NewInt(42), DepositNonce: bridge.NewNonce(1), - Status: bridge.Executed, }, }, + Statuses: []byte{bridge.Executed}, } got := c.GetActionIdForSetStatusOnPendingTransfer(context.TODO(), batch) @@ -725,6 +726,7 @@ func TestClient_GetTransactionsStatuses(t *testing.T) { c := &client{ proxy: proxy, bridgeAddress: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede", + log: logger.GetOrCreate("test"), } c.address, _ = data.NewAddressFromBech32String("erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede") diff --git a/bridge/eth/client.go b/bridge/eth/client.go index d568aee4..29a8187a 100644 --- a/bridge/eth/client.go +++ b/bridge/eth/client.go @@ -189,6 +189,7 @@ func (c *client) GetPending(ctx context.Context) (*bridge.Batch, error) { result = &bridge.Batch{ Id: batch.Nonce, Transactions: transactions, + Statuses: make([]byte, len(transactions)), } } @@ -265,12 +266,12 @@ func (c *client) GetTransactionsStatuses(ctx context.Context, batchId bridge.Bat } // Sign will sign upon the provided batch and send the signatures through the broadcaster to other relayers -func (c *client) Sign(_ context.Context, action bridge.ActionId, batch *bridge.Batch) (string, error) { +func (c *client) Sign(ctx context.Context, action bridge.ActionId, batch *bridge.Batch) (string, error) { switch int64FromActionId(action) { case transferAction: c.broadcastSignatureForTransfer(batch) case setStatusAction: - c.broadcastSignatureForFinish(batch) + c.broadcastSignatureForFinish(ctx, batch) } return "", nil @@ -318,7 +319,7 @@ func (c *client) Execute( var transaction *types.Transaction - msgHash, err := c.generateMsgHash(batch, action) + msgHash, err := c.generateMsgHash(ctx, batch, action) if err != nil { return "", fmt.Errorf("ETH: %w", err) } @@ -339,7 +340,7 @@ func (c *client) Execute( case transferAction: transaction, err = c.transfer(ctx, auth, signatures, batch) case setStatusAction: - transaction, err = c.finish(auth, signatures, batch) + transaction, err = c.finish(ctx, auth, signatures, batch) } if err != nil { @@ -437,10 +438,10 @@ func (c *client) checkCumulatedTransfers(ctx context.Context, transfers map[comm return nil } -func (c *client) finish(auth *bind.TransactOpts, signatures [][]byte, batch *bridge.Batch) (*types.Transaction, error) { - var proposedStatuses []uint8 - for _, tx := range batch.Transactions { - proposedStatuses = append(proposedStatuses, tx.Status) +func (c *client) finish(ctx context.Context, auth *bind.TransactOpts, signatures [][]byte, batch *bridge.Batch) (*types.Transaction, error) { + proposedStatuses, err := c.fixStatuses(ctx, batch) + if err != nil { + return nil, err } c.log.Debug("client.finish", "auth", transactOptsToString(auth), @@ -451,6 +452,7 @@ func (c *client) finish(auth *bind.TransactOpts, signatures [][]byte, batch *bri // SignersCount will return the total signers number that sent the signatures on the required message hash func (c *client) SignersCount( + ctx context.Context, batch *bridge.Batch, actionId bridge.ActionId, sigHolder bridge.SignaturesHolder, @@ -461,7 +463,7 @@ func (c *client) SignersCount( return 0 } - msgHash, err := c.generateMsgHash(batch, actionId) + msgHash, err := c.generateMsgHash(ctx, batch, actionId) if err != nil { c.log.Error(err.Error()) @@ -545,12 +547,12 @@ func amounts(transactions []*bridge.DepositTransaction) []*big.Int { return result } -func (c *client) generateMsgHash(batch *bridge.Batch, actionId bridge.ActionId) (common.Hash, error) { +func (c *client) generateMsgHash(ctx context.Context, batch *bridge.Batch, actionId bridge.ActionId) (common.Hash, error) { switch int64FromActionId(actionId) { case transferAction: return c.generateMsgHashForTransfer(batch) case setStatusAction: - return c.generateMsgHashForFinish(batch) + return c.generateMsgHashForFinish(ctx, batch) } return common.Hash{}, fmt.Errorf("Client.generateMsgHash not implemented for action ID %v", actionId) @@ -571,10 +573,23 @@ func (c *client) generateMsgHashForTransfer(batch *bridge.Batch) (common.Hash, e return crypto.Keccak256Hash(append([]byte(messagePrefix), hash.Bytes()...)), nil } -func (c *client) generateMsgHashForFinish(batch *bridge.Batch) (common.Hash, error) { - var statuses []uint8 - for _, tx := range batch.Transactions { - statuses = append(statuses, tx.Status) +func (c *client) fixStatuses(ctx context.Context, batch *bridge.Batch) ([]byte, error) { + newBatch, err := c.GetPending(ctx) + if err != nil { + return nil, err + } + batch.ResolveNewDeposits(len(newBatch.Statuses)) + + clonedStatuses := make([]byte, len(batch.Statuses)) + copy(clonedStatuses, batch.Statuses) + + return clonedStatuses, nil +} + +func (c *client) generateMsgHashForFinish(ctx context.Context, batch *bridge.Batch) (common.Hash, error) { + statuses, err := c.fixStatuses(ctx, batch) + if err != nil { + return common.Hash{}, err } arguments, err := finishCurrentPendingTransactionArgs() @@ -591,8 +606,8 @@ func (c *client) generateMsgHashForFinish(batch *bridge.Batch) (common.Hash, err return crypto.Keccak256Hash(append([]byte(messagePrefix), hash.Bytes()...)), nil } -func (c *client) broadcastSignatureForFinish(batch *bridge.Batch) { - msgHash, err := c.generateMsgHashForFinish(batch) +func (c *client) broadcastSignatureForFinish(ctx context.Context, batch *bridge.Batch) { + msgHash, err := c.generateMsgHashForFinish(ctx, batch) if err != nil { c.log.Error(err.Error()) return diff --git a/bridge/eth/client_test.go b/bridge/eth/client_test.go index 29d07f23..29f3ebb3 100644 --- a/bridge/eth/client_test.go +++ b/bridge/eth/client_test.go @@ -73,6 +73,7 @@ func TestGetPending(t *testing.T) { Amount: big.NewInt(42), }, }, + Statuses: make([]byte, 1), }, }, { @@ -127,9 +128,10 @@ func TestSign(t *testing.T) { t.Run("will sign propose status for executed tx", func(t *testing.T) { batch := &bridge.Batch{ Id: bridge.NewBatchId(42), - Transactions: []*bridge.DepositTransaction{{ - Status: bridge.Executed, - }}, + Transactions: []*bridge.DepositTransaction{ + {}, + }, + Statuses: []byte{bridge.Executed}, } broadcaster, c := buildStubs() c.GetActionIdForSetStatusOnPendingTransfer(context.TODO(), batch) @@ -147,9 +149,10 @@ func TestSign(t *testing.T) { t.Run("will sign propose status for rejected tx", func(t *testing.T) { batch := &bridge.Batch{ Id: bridge.NewBatchId(42), - Transactions: []*bridge.DepositTransaction{{ - Status: bridge.Rejected, - }}, + Transactions: []*bridge.DepositTransaction{ + {}, + }, + Statuses: []byte{bridge.Rejected}, } broadcaster, c := buildStubs() c.GetActionIdForSetStatusOnPendingTransfer(context.TODO(), batch) @@ -249,12 +252,12 @@ func TestSignersCount(t *testing.T) { } t.Run("should return 0 when sig holder is nil", func(t *testing.T) { - got := c.SignersCount(batch, bridge.NewActionId(0), nil) + got := c.SignersCount(context.TODO(), batch, bridge.NewActionId(0), nil) assert.Equal(t, uint(0), got) }) t.Run("should return signature", func(t *testing.T) { - got := c.SignersCount(batch, bridge.NewActionId(0), sigHolderStub) + got := c.SignersCount(context.TODO(), batch, bridge.NewActionId(0), sigHolderStub) assert.Equal(t, uint(1), got) }) diff --git a/bridge/interface.go b/bridge/interface.go index 0a80410d..f7203ca6 100644 --- a/bridge/interface.go +++ b/bridge/interface.go @@ -39,7 +39,7 @@ type Bridge interface { WasExecuted(context.Context, ActionId, BatchId) bool Sign(context.Context, ActionId, *Batch) (string, error) Execute(context.Context, ActionId, *Batch, SignaturesHolder) (string, error) - SignersCount(*Batch, ActionId, SignaturesHolder) uint + SignersCount(context.Context, *Batch, ActionId, SignaturesHolder) uint GetTransactionsStatuses(ctx context.Context, batchID BatchId) ([]uint8, error) IsInterfaceNil() bool } diff --git a/bridge/models.go b/bridge/models.go index 4a0d9e70..5dd5ff9f 100644 --- a/bridge/models.go +++ b/bridge/models.go @@ -3,10 +3,16 @@ package bridge import ( "fmt" "math/big" + + logger "github.com/ElrondNetwork/elrond-go-logger" ) +var log = logger.GetOrCreate("bridge/models") + const ( + // Executed is the Executed with success status value Executed = uint8(3) + // Rejected is the Rejected status value Rejected = uint8(4) ) @@ -26,6 +32,7 @@ func NewActionId(value int64) ActionId { return big.NewInt(value) } +// DepositTransaction represents a deposit transaction ready to be executed on the other chain type DepositTransaction struct { To string DisplayableTo string @@ -34,24 +41,49 @@ type DepositTransaction struct { Amount *big.Int DepositNonce Nonce BlockNonce Nonce - Status uint8 Error error } // String will convert the deposit transaction to a string func (dt *DepositTransaction) String() string { return fmt.Sprintf("to: %s, from: %s, token address: %s, amount: %v, deposit nonce: %v, block nonce: %v, "+ - "status: %d, error: %v", dt.DisplayableTo, dt.From, dt.TokenAddress, dt.Amount, dt.DepositNonce, dt.BlockNonce, dt.Status, dt.Error) + "error: %v", dt.DisplayableTo, dt.From, dt.TokenAddress, dt.Amount, dt.DepositNonce, dt.BlockNonce, dt.Error) } +// Batch represents the transactions batch to be executed type Batch struct { Id BatchId Transactions []*DepositTransaction + Statuses []byte } -func (batch *Batch) SetStatusOnAllTransactions(status uint8, err error) { +// SetStatusOnAllTransactions will set the provided status on all existing transactions +func (batch *Batch) SetStatusOnAllTransactions(status byte, err error) { for _, tx := range batch.Transactions { - tx.Status = status tx.Error = err } + + for i := 0; i < len(batch.Statuses); i++ { + batch.Statuses[i] = status + } +} + +// ResolveNewDeposits will add new statuses as rejected if the newNumDeposits exceeds the number of the deposits +func (batch *Batch) ResolveNewDeposits(newNumDeposits int) { + oldLen := len(batch.Statuses) + if newNumDeposits == oldLen { + log.Debug("num statuses ok", "len statuses", oldLen) + return + } + + if newNumDeposits < oldLen { + log.Error("num statuses unrecoverable", "len statuses", oldLen, "new num deposits", newNumDeposits) + return + } + + for newNumDeposits > len(batch.Statuses) { + batch.Statuses = append(batch.Statuses, Rejected) + } + + log.Warn("recovered num statuses", "len statuses", oldLen, "new num deposits", newNumDeposits) } diff --git a/bridge/models_test.go b/bridge/models_test.go new file mode 100644 index 00000000..f414c48a --- /dev/null +++ b/bridge/models_test.go @@ -0,0 +1,33 @@ +package bridge + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBatch_ResolveNewDeposits(t *testing.T) { + t.Parallel() + + batch := &Batch{ + Transactions: []*DepositTransaction{ + { + To: "to1", + }, + { + To: "to2", + }, + }, + Statuses: make([]byte, 2), + } + + for i := 0; i < 3; i++ { + batch.ResolveNewDeposits(i) + assert.Equal(t, 2, len(batch.Statuses)) + } + + batch.ResolveNewDeposits(3) + assert.Equal(t, 3, len(batch.Statuses)) + assert.Equal(t, Rejected, batch.Statuses[2]) + assert.Equal(t, byte(0), batch.Statuses[0]+batch.Statuses[1]) +} diff --git a/cmd/bridge/config/config.toml b/cmd/bridge/config/config.toml index 1c23c019..8bbc4746 100644 --- a/cmd/bridge/config/config.toml +++ b/cmd/bridge/config/config.toml @@ -2,7 +2,7 @@ NetworkAddress = "http://127.0.0.1:8545" # a network address BridgeAddress = "3009d97FfeD62E57d444e552A9eDF9Ee6Bc8644c" # the eth address for the bridge contract SafeContractAddress = "A6504Cc508889bbDBd4B748aFf6EA6b5D0d2684c" - PrivateKeyFile = "config/ethereum.sk" # the path to the file containing the relayer eth private key + PrivateKeyFile = "keys/ethereum.sk" # the path to the file containing the relayer eth private key GasLimit = 500000 ERC20Contracts = ["d1135C0307CEB01FD4728db8e5B8D38fbf984F9a"] [Eth.GasStation] @@ -17,7 +17,7 @@ [Elrond] NetworkAddress = "https://devnet-gateway.elrond.com" # the network address BridgeAddress = "erd1qqqqqqqqqqqqqpgqzyuaqg3dl7rqlkudrsnm5ek0j3a97qevd8sszj0glf" # the elrond address for the bridge contract - PrivateKeyFile = "config/elrond.pem" # the path to the pem file containing the relayer elrond wallet + PrivateKeyFile = "keys/elrond.pem" # the path to the pem file containing the relayer elrond wallet IntervalToResendTxsInSeconds = 60 # the time in seconds between nonce reads [Elrond.GasMap] Sign = 8000000 diff --git a/cmd/bridge/config/config_binance.toml b/cmd/bridge/config/config_binance.toml deleted file mode 100644 index 89260728..00000000 --- a/cmd/bridge/config/config_binance.toml +++ /dev/null @@ -1,91 +0,0 @@ -[Eth] - NetworkAddress = "http://127.0.0.1:8545" # a network address - BridgeAddress = "3009d97FfeD62E57d444e552A9eDF9Ee6Bc8644c" # the eth address for the bridge contract - SafeContractAddress = "A6504Cc508889bbDBd4B748aFf6EA6b5D0d2684c" - PrivateKeyFile = "config/ethereum.sk" # the path to the file containing the relayer eth private key - GasLimit = 500000 - ERC20Contracts = ["d1135C0307CEB01FD4728db8e5B8D38fbf984F9a"] - [Eth.GasStation] - Enabled = false - URL = "https://ethgasstation.info/api/ethgasAPI.json?" # gas station URL. Suggestion to provide the api-key here - PollingIntervalInSeconds = 60 # number of seconds between gas price polling - RequestTimeInSeconds = 2 # maximum timeout (in seconds) for the gas price request - MaximumAllowedGasPrice = 3000 # maximum value allowed for the fetched gas price value - # GasPriceSelector available options: "fast", "fastest", "safeLow", "average" - GasPriceSelector = "safeLow" # selector used to provide the gas price - -[Elrond] - NetworkAddress = "https://devnet-gateway.elrond.com" # the network address - BridgeAddress = "erd1qqqqqqqqqqqqqpgqzyuaqg3dl7rqlkudrsnm5ek0j3a97qevd8sszj0glf" # the elrond address for the bridge contract - PrivateKeyFile = "config/elrond.pem" # the path to the pem file containing the relayer elrond wallet - IntervalToResendTxsInSeconds = 60 # the time in seconds between nonce reads - [Elrond.GasMap] - Sign = 8000000 - ProposeTransferBase = 11000000 - ProposeTransferForEach = 3000000 - ProposeStatus = 30000000 - PerformActionBase = 25000000 - PerformActionForEach = 2000000 - -[P2P] - Port = "10010" - Seed = "" - InitialPeerList = [] - ProtocolID = "/erd/relay/2.0.0" - -[Relayer] - [Relayer.Marshalizer] - Type = "gogo protobuf" - SizeCheckDelta = 10 - [Relayer.RoleProvider] - UsePolling = true - PollingIntervalInMillis = 60000 # 1 minute - [Relayer.StatusMetricsStorage] - [Relayer.StatusMetricsStorage.Cache] - Name = "StatusMetricsStorage" - Capacity = 1000 - Type = "LRU" - [Relayer.StatusMetricsStorage.DB] - FilePath = "StatusMetricsStorageDB" - Type = "LvlDBSerial" - BatchDelaySeconds = 2 - MaxBatchSize = 100 - MaxOpenFiles = 10 - -[StateMachine] - [StateMachine.EthToElrond] - StepDurationInMillis = 6000 - Steps = [ - { Name = "getting the pending transactions", DurationInMillis = 12000 }, - { Name = "proposing transfer", DurationInMillis = 12000 }, - { Name = "waiting signatures for propose transfer", DurationInMillis = 12000 }, - { Name = "executing transfer", DurationInMillis = 12000 }, - { Name = "proposing set status", DurationInMillis = 120000 }, # 2 minutes - { Name = "waiting signatures for propose set status", DurationInMillis = 12000 }, - { Name = "executing set status", DurationInMillis = 12000 } - ] - [StateMachine.ElrondToEth] - StepDurationInMillis = 6000 - Steps = [ - { Name = "getting the pending transactions", DurationInMillis = 12000 }, - { Name = "proposing transfer", DurationInMillis = 12000 }, - { Name = "waiting signatures for propose transfer", DurationInMillis = 12000 }, - { Name = "executing transfer", DurationInMillis = 120000 }, # 2 minutes - { Name = "proposing set status", DurationInMillis = 12000 }, - { Name = "waiting signatures for propose set status", DurationInMillis = 12000 }, - { Name = "executing set status", DurationInMillis = 12000 } - ] -[Logs] - LogFileLifeSpanInSec = 86400 # 24h - -[Antiflood] - Enabled = true - [Antiflood.WebServer] - # SimultaneousRequests represents the number of concurrent requests accepted by the web server - # this is a global throttler that acts on all http connections regardless of the originating source - SimultaneousRequests = 100 - # SameSourceRequests defines how many requests are allowed from the same source in the specified - # time frame (SameSourceResetIntervalInSec) - SameSourceRequests = 10000 - # SameSourceResetIntervalInSec time frame between counter reset, in seconds - SameSourceResetIntervalInSec = 1 diff --git a/cmd/bridge/config/config_ethereum.toml b/cmd/bridge/config/config_ethereum.toml deleted file mode 100644 index c2c7734a..00000000 --- a/cmd/bridge/config/config_ethereum.toml +++ /dev/null @@ -1,91 +0,0 @@ -[Eth] - NetworkAddress = "http://127.0.0.1:8545" # a network address - BridgeAddress = "eB6859d7Fc1A47E2630dB83E244829Cb4A814888" # the eth address for the bridge contract - SafeContractAddress = "3A06278d31FA803320fB76Fc3C754AAEef842729" - PrivateKeyFile = "config/ethereum.sk" # the path to the file containing the relayer eth private key - GasLimit = 500000 - ERC20Contracts = ["ba3b1bF0b572aA6555718A12Af7CC63e0D103A26"] - [Eth.GasStation] - Enabled = true - URL = "https://ethgasstation.info/api/ethgasAPI.json?" # gas station URL. Suggestion to provide the api-key here - PollingIntervalInSeconds = 60 # number of seconds between gas price polling - RequestTimeInSeconds = 2 # maximum timeout (in seconds) for the gas price request - MaximumAllowedGasPrice = 3000 # maximum value allowed for the fetched gas price value - # GasPriceSelector available options: "fast", "fastest", "safeLow", "average" - GasPriceSelector = "safeLow" # selector used to provide the gas price - -[Elrond] - NetworkAddress = "https://testnet-gateway.elrond.com" # the network address - BridgeAddress = "erd1qqqqqqqqqqqqqpgqgewxhtrgjv4jzhgs9y0q9pnkdkkmqlnxd8sszjrhzv" # the elrond address for the bridge contract - PrivateKeyFile = "config/elrond.pem" # the path to the pem file containing the relayer elrond wallet - IntervalToResendTxsInSeconds = 60 # the time in seconds between nonce reads - [Elrond.GasMap] - Sign = 8000000 - ProposeTransferBase = 11000000 - ProposeTransferForEach = 3000000 - ProposeStatus = 30000000 - PerformActionBase = 25000000 - PerformActionForEach = 2000000 - -[P2P] - Port = "10010" - Seed = "" - InitialPeerList = [] - ProtocolID = "/erd/relay/1.0.0" - -[Relayer] - [Relayer.Marshalizer] - Type = "gogo protobuf" - SizeCheckDelta = 10 - [Relayer.RoleProvider] - UsePolling = true - PollingIntervalInMillis = 60000 # 1 minute - [Relayer.StatusMetricsStorage] - [Relayer.StatusMetricsStorage.Cache] - Name = "StatusMetricsStorage" - Capacity = 1000 - Type = "LRU" - [Relayer.StatusMetricsStorage.DB] - FilePath = "StatusMetricsStorageDB" - Type = "LvlDBSerial" - BatchDelaySeconds = 2 - MaxBatchSize = 100 - MaxOpenFiles = 10 - -[StateMachine] - [StateMachine.EthToElrond] - StepDurationInMillis = 6000 - Steps = [ - { Name = "getting the pending transactions", DurationInMillis = 12000 }, - { Name = "proposing transfer", DurationInMillis = 12000 }, - { Name = "waiting signatures for propose transfer", DurationInMillis = 12000 }, - { Name = "executing transfer", DurationInMillis = 12000 }, - { Name = "proposing set status", DurationInMillis = 600000 }, # 10 minutes - { Name = "waiting signatures for propose set status", DurationInMillis = 12000 }, - { Name = "executing set status", DurationInMillis = 12000 } - ] - [StateMachine.ElrondToEth] - StepDurationInMillis = 6000 - Steps = [ - { Name = "getting the pending transactions", DurationInMillis = 12000 }, - { Name = "proposing transfer", DurationInMillis = 12000 }, - { Name = "waiting signatures for propose transfer", DurationInMillis = 12000 }, - { Name = "executing transfer", DurationInMillis = 600000 }, # 10 minutes - { Name = "proposing set status", DurationInMillis = 12000 }, - { Name = "waiting signatures for propose set status", DurationInMillis = 12000 }, - { Name = "executing set status", DurationInMillis = 12000 } - ] -[Logs] - LogFileLifeSpanInSec = 86400 # 24h - -[Antiflood] - Enabled = true - [Antiflood.WebServer] - # SimultaneousRequests represents the number of concurrent requests accepted by the web server - # this is a global throttler that acts on all http connections regardless of the originating source - SimultaneousRequests = 100 - # SameSourceRequests defines how many requests are allowed from the same source in the specified - # time frame (SameSourceResetIntervalInSec) - SameSourceRequests = 10000 - # SameSourceResetIntervalInSec time frame between counter reset, in seconds - SameSourceResetIntervalInSec = 1 diff --git a/cmd/bridge/config/elrond.pem b/cmd/bridge/keys/elrond.pem similarity index 100% rename from cmd/bridge/config/elrond.pem rename to cmd/bridge/keys/elrond.pem diff --git a/cmd/bridge/config/ethereum.sk b/cmd/bridge/keys/ethereum.sk similarity index 100% rename from cmd/bridge/config/ethereum.sk rename to cmd/bridge/keys/ethereum.sk diff --git a/ethToElrond/bridgeExecutors/ethElrondBridgeExecutor.go b/ethToElrond/bridgeExecutors/ethElrondBridgeExecutor.go index f1302c8f..15bcddb8 100644 --- a/ethToElrond/bridgeExecutors/ethElrondBridgeExecutor.go +++ b/ethToElrond/bridgeExecutors/ethElrondBridgeExecutor.go @@ -128,7 +128,7 @@ func (executor *ethElrondBridgeExecutor) IsQuorumReachedForProposeTransfer(ctx c } func (executor *ethElrondBridgeExecutor) isQuorumReachedOnBridge(ctx context.Context, bridge bridge.Bridge) bool { - count := bridge.SignersCount(executor.pendingBatch, executor.actionID, executor) + count := bridge.SignersCount(ctx, executor.pendingBatch, executor.actionID, executor) quorum, err := executor.quorumProvider.GetQuorum(ctx) if err != nil { executor.logger.Error(executor.appendMessageToName(err.Error())) @@ -263,12 +263,18 @@ func (executor *ethElrondBridgeExecutor) UpdateTransactionsStatusesIfNeeded(ctx } if len(statuses) != len(executor.pendingBatch.Transactions) { - return fmt.Errorf("%w for batch ID %v", ErrBatchIDStatusMismatch, batchId) + executor.logger.Warn("pending transaction len mismatch in UpdateTransactionsStatusesIfNeeded", + "batchID", batchId, "statuses", len(statuses), + "num pending batch transactions", len(executor.pendingBatch.Transactions)) } - for i, tx := range executor.pendingBatch.Transactions { - tx.Status = statuses[i] - executor.updateStatusInStatusHandler(tx.Status) + for i := 0; i < len(executor.pendingBatch.Transactions) && i < len(statuses); i++ { + executor.pendingBatch.Statuses[i] = statuses[i] + executor.updateStatusInStatusHandler(statuses[i]) + } + for i := len(statuses); i < len(executor.pendingBatch.Statuses); i++ { + executor.pendingBatch.Statuses[i] = bridge.Rejected + executor.updateStatusInStatusHandler(bridge.Rejected) } executor.statusHandler.AddIntMetric(core.MetricNumBatches, 1) @@ -293,8 +299,8 @@ func (executor *ethElrondBridgeExecutor) isStatusesCheckOnDestinationNeeded() bo return false } // if all statuses are rejected, there was an error, so we do not need to check the statuses on destination - for _, tx := range executor.pendingBatch.Transactions { - if tx.Status != bridge.Rejected { + for _, stat := range executor.pendingBatch.Statuses { + if stat != bridge.Rejected { return true } } diff --git a/ethToElrond/bridgeExecutors/ethElrondBridgeExecutor_test.go b/ethToElrond/bridgeExecutors/ethElrondBridgeExecutor_test.go index b20ed75b..099c5009 100644 --- a/ethToElrond/bridgeExecutors/ethElrondBridgeExecutor_test.go +++ b/ethToElrond/bridgeExecutors/ethElrondBridgeExecutor_test.go @@ -369,7 +369,7 @@ func TestIsQuorumReachedForProposeTransfer(t *testing.T) { }, } db := testsCommon.NewBridgeStub() - db.SignersCountCalled = func(_ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { + db.SignersCountCalled = func(ctx context.Context, _ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { return 2 } args.DestinationBridge = db @@ -388,7 +388,7 @@ func TestIsQuorumReachedForProposeTransfer(t *testing.T) { } db := testsCommon.NewBridgeStub() - db.SignersCountCalled = func(_ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { + db.SignersCountCalled = func(ctx context.Context, _ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { return 3 } args.DestinationBridge = db @@ -407,7 +407,7 @@ func TestIsQuorumReachedForProposeTransfer(t *testing.T) { } db := testsCommon.NewBridgeStub() - db.SignersCountCalled = func(_ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { + db.SignersCountCalled = func(ctx context.Context, _ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { return 4 } args.DestinationBridge = db @@ -457,7 +457,7 @@ func TestIsQuorumReachedForProposeSetStatus(t *testing.T) { } sb := testsCommon.NewBridgeStub() - sb.SignersCountCalled = func(_ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { + sb.SignersCountCalled = func(ctx context.Context, _ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { return 2 } args.SourceBridge = sb @@ -476,7 +476,7 @@ func TestIsQuorumReachedForProposeSetStatus(t *testing.T) { } sb := testsCommon.NewBridgeStub() - sb.SignersCountCalled = func(_ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { + sb.SignersCountCalled = func(ctx context.Context, _ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { return 3 } args.SourceBridge = sb @@ -495,7 +495,7 @@ func TestIsQuorumReachedForProposeSetStatus(t *testing.T) { } sb := testsCommon.NewBridgeStub() - sb.SignersCountCalled = func(_ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { + sb.SignersCountCalled = func(ctx context.Context, _ *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { return 4 } args.SourceBridge = sb @@ -696,6 +696,7 @@ func TestSetStatusRejectedOnAllTransactions(t *testing.T) { {To: "address2", DepositNonce: bridge.NewNonce(1)}, {To: "address3", DepositNonce: bridge.NewNonce(2)}, }, + Statuses: make([]byte, 3), } expectedError := errors.New("some error") args := createMockArgs() @@ -713,9 +714,12 @@ func TestSetStatusRejectedOnAllTransactions(t *testing.T) { assert.True(t, executor.HasPendingBatch()) executor.SetStatusRejectedOnAllTransactions(expectedError) for _, transaction := range executor.pendingBatch.Transactions { - assert.Equal(t, bridge.Rejected, transaction.Status) assert.Equal(t, expectedError, transaction.Error) } + for _, stat := range executor.pendingBatch.Statuses { + assert.Equal(t, bridge.Rejected, stat) + } + assert.Equal(t, len(executor.pendingBatch.Transactions), len(executor.pendingBatch.Statuses)) } func TestSignProposeTransferOnDestination(t *testing.T) { @@ -877,10 +881,9 @@ func TestUpdateTransactionsStatusesAccordingToDestination(t *testing.T) { require.Nil(t, err) batch := &bridge.Batch{ Transactions: []*bridge.DepositTransaction{ - { - Status: 0, - }, + {}, }, + Statuses: make([]byte, 1), } executor.SetPendingBatch(batch) @@ -900,11 +903,14 @@ func TestUpdateTransactionsStatusesAccordingToDestination(t *testing.T) { Transactions: []*bridge.DepositTransaction{ {}, }, + Statuses: make([]byte, 1), } executor.SetPendingBatch(batch) err = executor.UpdateTransactionsStatusesIfNeeded(context.TODO()) - assert.True(t, errors.Is(err, ErrBatchIDStatusMismatch)) + assert.Nil(t, err) + assert.Equal(t, 1, len(batch.Statuses)) + assert.Equal(t, bridge.Rejected, batch.Statuses[0]) }) t.Run("destinationBridge.GetTransactionsStatuses sets the status", func(t *testing.T) { args := createMockArgs() @@ -922,7 +928,9 @@ func TestUpdateTransactionsStatusesAccordingToDestination(t *testing.T) { executor, err := NewEthElrondBridgeExecutor(args) require.Nil(t, err) - batch := &bridge.Batch{} + batch := &bridge.Batch{ + Statuses: make([]byte, numTxs), + } for i := 0; i < numTxs; i++ { batch.Transactions = append(batch.Transactions, &bridge.DepositTransaction{}) } @@ -932,8 +940,9 @@ func TestUpdateTransactionsStatusesAccordingToDestination(t *testing.T) { assert.Nil(t, err) assert.Equal(t, numTxs, len(batch.Transactions)) // extra-protection that the number of txs was not modified + assert.Equal(t, numTxs, len(batch.Statuses)) for i := 0; i < numTxs; i++ { - assert.Equal(t, byte(i), batch.Transactions[i].Status) + assert.Equal(t, byte(i), batch.Statuses[i]) } }) t.Run("destinationBridge.GetTransactionsStatuses rejected transactions should not call destination bridge", func(t *testing.T) { @@ -955,10 +964,9 @@ func TestUpdateTransactionsStatusesAccordingToDestination(t *testing.T) { batch := &bridge.Batch{} for i := 0; i < numTxs; i++ { - tx := &bridge.DepositTransaction{ - Status: bridge.Rejected, - } + tx := &bridge.DepositTransaction{} batch.Transactions = append(batch.Transactions, tx) + batch.Statuses = append(batch.Statuses, bridge.Rejected) } executor.SetPendingBatch(batch) @@ -966,8 +974,9 @@ func TestUpdateTransactionsStatusesAccordingToDestination(t *testing.T) { assert.Nil(t, err) assert.Equal(t, numTxs, len(batch.Transactions)) // extra-protection that the number of txs was not modified + assert.Equal(t, numTxs, len(batch.Statuses)) for i := 0; i < numTxs; i++ { - assert.Equal(t, bridge.Rejected, batch.Transactions[i].Status) + assert.Equal(t, bridge.Rejected, batch.Statuses[i]) } }) t.Run("destinationBridge.GetTransactionsStatuses nil pending batch should not call destination bridge", func(t *testing.T) { @@ -1002,13 +1011,14 @@ func TestUpdateTransactionsStatusesAccordingToDestination(t *testing.T) { batch := &bridge.Batch{} for i := 0; i < numTxs; i++ { - tx := &bridge.DepositTransaction{ - Status: bridge.Rejected, - } + tx := &bridge.DepositTransaction{} + stat := bridge.Rejected if i == numTxs-1 { - tx.Status = bridge.Executed + stat = bridge.Executed } + batch.Transactions = append(batch.Transactions, tx) + batch.Statuses = append(batch.Statuses, stat) } executor.SetPendingBatch(batch) @@ -1016,8 +1026,58 @@ func TestUpdateTransactionsStatusesAccordingToDestination(t *testing.T) { assert.Nil(t, err) assert.Equal(t, numTxs, len(batch.Transactions)) // extra-protection that the number of txs was not modified + assert.Equal(t, numTxs, len(batch.Statuses)) for i := 0; i < numTxs; i++ { - assert.Equal(t, statuses[i], batch.Transactions[i].Status) + assert.Equal(t, statuses[i], batch.Statuses[i]) + } + }) + t.Run("destinationBridge.GetTransactionsStatuses more statuses received should ignore extra statuses", func(t *testing.T) { + args := createMockArgs() + db := testsCommon.NewBridgeStub() + db.GetTransactionsStatusesCalled = func(ctx context.Context, batchID bridge.BatchId) ([]uint8, error) { + return []byte{bridge.Executed, bridge.Executed, bridge.Rejected}, nil + } + args.DestinationBridge = db + executor, err := NewEthElrondBridgeExecutor(args) + require.Nil(t, err) + batch := &bridge.Batch{ + Transactions: []*bridge.DepositTransaction{ + {}, + {}, + }, + Statuses: make([]byte, 2), } + executor.SetPendingBatch(batch) + + err = executor.UpdateTransactionsStatusesIfNeeded(context.TODO()) + assert.Nil(t, err) + assert.Equal(t, 2, len(batch.Statuses)) + expectedStatuses := []byte{bridge.Executed, bridge.Executed} + assert.Equal(t, expectedStatuses, batch.Statuses) + }) + t.Run("destinationBridge.GetTransactionsStatuses less statuses received should put reject on extra", func(t *testing.T) { + args := createMockArgs() + db := testsCommon.NewBridgeStub() + db.GetTransactionsStatusesCalled = func(ctx context.Context, batchID bridge.BatchId) ([]uint8, error) { + return []byte{bridge.Executed, bridge.Rejected}, nil + } + args.DestinationBridge = db + executor, err := NewEthElrondBridgeExecutor(args) + require.Nil(t, err) + batch := &bridge.Batch{ + Transactions: []*bridge.DepositTransaction{ + {}, + {}, + {}, + }, + Statuses: make([]byte, 3), + } + executor.SetPendingBatch(batch) + + err = executor.UpdateTransactionsStatusesIfNeeded(context.TODO()) + assert.Nil(t, err) + assert.Equal(t, 3, len(batch.Statuses)) + expectedStatuses := []byte{bridge.Executed, bridge.Rejected, bridge.Rejected} + assert.Equal(t, expectedStatuses, batch.Statuses) }) } diff --git a/integrationTests/ethToElrond/stateMachineExecutor_test.go b/integrationTests/ethToElrond/stateMachineExecutor_test.go index cced1c83..b7827477 100644 --- a/integrationTests/ethToElrond/stateMachineExecutor_test.go +++ b/integrationTests/ethToElrond/stateMachineExecutor_test.go @@ -38,7 +38,6 @@ func TestBridgeExecutorWithStateMachineOnCompleteExecutionFlow(t *testing.T) { Amount: big.NewInt(1000), DepositNonce: big.NewInt(2), BlockNonce: big.NewInt(2000000), - Status: 0, Error: nil, }, { @@ -48,10 +47,10 @@ func TestBridgeExecutorWithStateMachineOnCompleteExecutionFlow(t *testing.T) { Amount: big.NewInt(1001), DepositNonce: big.NewInt(3), BlockNonce: big.NewInt(2000001), - Status: 0, Error: nil, }, }, + Statuses: make([]byte, 2), } sourceBridge.SetPending(pendingBatch) sourceBridge.SetActionID(sourceActionID) @@ -108,7 +107,6 @@ func TestBridgeExecutorWithStateMachineFailedToProposeTransfer(t *testing.T) { Amount: big.NewInt(1000), DepositNonce: big.NewInt(2), BlockNonce: big.NewInt(2000000), - Status: 0, Error: nil, }, { @@ -118,10 +116,10 @@ func TestBridgeExecutorWithStateMachineFailedToProposeTransfer(t *testing.T) { Amount: big.NewInt(1001), DepositNonce: big.NewInt(3), BlockNonce: big.NewInt(2000001), - Status: 0, Error: nil, }, }, + Statuses: make([]byte, 2), } sourceBridge.SetPending(pendingBatch) sourceBridge.SetActionID(sourceActionID) @@ -259,8 +257,8 @@ func checkStatusWhenExecutedOnSource( proposedStatusBatch := sourceBridge.GetProposedSetStatusBatch() require.Equal(t, len(pendingBatch.Transactions), len(proposedStatusBatch.Transactions)) - for i, tx := range proposedStatusBatch.Transactions { - assert.Equal(t, expectedStatuses[i], tx.Status) + for i, stat := range proposedStatusBatch.Statuses { + assert.Equal(t, expectedStatuses[i], stat) } assert.Nil(t, sourceBridge.GetProposedTransferBatch()) diff --git a/integrationTests/mock/elrondChainMock.go b/integrationTests/mock/elrondChainMock.go index 0d8bfcc1..886803d9 100644 --- a/integrationTests/mock/elrondChainMock.go +++ b/integrationTests/mock/elrondChainMock.go @@ -165,6 +165,13 @@ func (mock *ElrondChainMock) SetPendingBatch(pendingBatch *ElrondPendingBatch) { mock.mutState.Unlock() } +// AddDepositToCurrentBatch - +func (mock *ElrondChainMock) AddDepositToCurrentBatch(deposit ElrondDeposit) { + mock.mutState.Lock() + mock.pendingBatch.ElrondDeposits = append(mock.pendingBatch.ElrondDeposits, deposit) + mock.mutState.Unlock() +} + // IsInterfaceNil - func (mock *ElrondChainMock) IsInterfaceNil() bool { return mock == nil diff --git a/integrationTests/mock/elrondContractStateMock.go b/integrationTests/mock/elrondContractStateMock.go index e6418e23..ac5362e9 100644 --- a/integrationTests/mock/elrondContractStateMock.go +++ b/integrationTests/mock/elrondContractStateMock.go @@ -62,6 +62,8 @@ type elrondContractStateMock struct { performedAction *big.Int pendingBatch *ElrondPendingBatch quorum int + + ProposeMultiTransferEsdtBatchCalled func() } func newElrondContractStateMock() *elrondContractStateMock { @@ -121,6 +123,10 @@ func (mock *elrondContractStateMock) proposeMultiTransferEsdtBatch(dataSplit []s transfer, hash := mock.createProposedTransfer(dataSplit) mock.proposedTransfers[hash] = transfer + + if mock.ProposeMultiTransferEsdtBatchCalled != nil { + mock.ProposeMultiTransferEsdtBatchCalled() + } } func (mock *elrondContractStateMock) createProposedStatus(dataSplit []string) (*ElrondProposedStatus, string) { @@ -142,6 +148,10 @@ func (mock *elrondContractStateMock) createProposedStatus(dataSplit []string) (* status.Statuses = append(status.Statuses, stat[0]) } + if len(status.Statuses) != len(mock.pendingBatch.ElrondDeposits) { + panic("different number of statuses fetched while creating proposed status") + } + hash, err := core.CalculateHash(integrationTests.TestMarshalizer, integrationTests.TestHasher, status) if err != nil { panic(err) diff --git a/integrationTests/mock/ethereumChainMock.go b/integrationTests/mock/ethereumChainMock.go index 0b1f9871..36fd45c7 100644 --- a/integrationTests/mock/ethereumChainMock.go +++ b/integrationTests/mock/ethereumChainMock.go @@ -2,6 +2,7 @@ package mock import ( "context" + "errors" "math/big" "sync" @@ -39,6 +40,8 @@ type EthereumChainMock struct { ProcessFinishedHandler func() quorum int relayers []common.Address + + ProposeMultiTransferEsdtBatchCalled func() } // NewEthereumChainMock - @@ -93,6 +96,13 @@ func (mock *EthereumChainMock) SetPendingBatch(batch contract.Batch) { mock.mutState.Unlock() } +// AddDepositToCurrentBatch - +func (mock *EthereumChainMock) AddDepositToCurrentBatch(deposit contract.Deposit) { + mock.mutState.Lock() + mock.pendingBatch.Deposits = append(mock.pendingBatch.Deposits, deposit) + mock.mutState.Unlock() +} + // FinishCurrentPendingBatch - func (mock *EthereumChainMock) FinishCurrentPendingBatch(_ *bind.TransactOpts, batchNonce *big.Int, newDepositStatuses []uint8, signatures [][]byte) (*types.Transaction, error) { status := &EthereumProposedStatus{ @@ -113,6 +123,11 @@ func (mock *EthereumChainMock) FinishCurrentPendingBatch(_ *bind.TransactOpts, b tx := types.NewTx(txData) mock.mutState.Lock() + if len(newDepositStatuses) != len(mock.pendingBatch.Deposits) { + mock.mutState.Unlock() + return nil, errors.New("status length mismatch") + } + mock.proposedStatus = status mock.pendingBatch = integrationTests.NoPendingBatch mock.mutState.Unlock() @@ -168,6 +183,10 @@ func (mock *EthereumChainMock) ExecuteTransfer(_ *bind.TransactOpts, tokens []co mock.proposedTransfer = proposedTransfer mock.mutState.Unlock() + if mock.ProposeMultiTransferEsdtBatchCalled != nil { + mock.ProposeMultiTransferEsdtBatchCalled() + } + return tx, nil } diff --git a/integrationTests/modelsOperations.go b/integrationTests/modelsOperations.go index a59c5a6f..122f3721 100644 --- a/integrationTests/modelsOperations.go +++ b/integrationTests/modelsOperations.go @@ -24,12 +24,16 @@ func CloneBatch(batch *bridge.Batch) *bridge.Batch { } newBatch := &bridge.Batch{ - Id: CloneBatchID(batch.Id), + Id: CloneBatchID(batch.Id), + Statuses: make([]byte, len(batch.Statuses)), } for _, tx := range batch.Transactions { newBatch.Transactions = append(newBatch.Transactions, CloneDepositTransaction(tx)) } + for i, stat := range batch.Statuses { + newBatch.Statuses[i] = stat + } return newBatch } @@ -65,7 +69,6 @@ func CloneDepositTransaction(tx *bridge.DepositTransaction) *bridge.DepositTrans Amount: big.NewInt(0).Set(tx.Amount), DepositNonce: CloneNonce(tx.DepositNonce), BlockNonce: CloneNonce(tx.BlockNonce), - Status: tx.Status, Error: tx.Error, } } diff --git a/integrationTests/relayers/elrondToEth_test.go b/integrationTests/relayers/elrondToEth_test.go index 8c62b6ce..2687ea33 100644 --- a/integrationTests/relayers/elrondToEth_test.go +++ b/integrationTests/relayers/elrondToEth_test.go @@ -116,6 +116,108 @@ func TestRelayersShouldExecuteTransferFromElrondToEth(t *testing.T) { } } +func TestRelayersShouldExecuteTransferFromElrondToEthIfTransactionsAppearInBatch(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + numTransactions := 2 + deposits, tokensAddresses, erc20Map := createTransactions(numTransactions) + + safeContractEthAddress := testsCommon.CreateRandomEthereumAddress() + erc20Contracts := make(map[common.Address]eth.Erc20Contract) + for addr, val := range erc20Map { + value := big.NewInt(0).Set(val) + erc20Contracts[addr] = &mockInteractors.Erc20ContractStub{ + BalanceOfCalled: func(ctx context.Context, account common.Address) (*big.Int, error) { + if account == safeContractEthAddress { + return value, nil + } + + return big.NewInt(0), nil + }, + } + } + + ethereumChainMock := mock.NewEthereumChainMock() + ethereumChainMock.SetQuorum(3) + expectedStatuses := []byte{bridge.Executed, bridge.Rejected} + ethereumChainMock.GetStatusesAfterExecutionHandler = func() []byte { + return expectedStatuses + } + elrondChainMock := mock.NewElrondChainMock() + for i := 0; i < len(deposits); i++ { + elrondChainMock.AddTokensPair(tokensAddresses[i], deposits[i].Ticker) + } + pendingBatch := mock.ElrondPendingBatch{ + Nonce: big.NewInt(1), + Timestamp: big.NewInt(0), + LastUpdatedBlockNumber: big.NewInt(0), + ElrondDeposits: deposits, + Status: 0, + } + elrondChainMock.SetPendingBatch(&pendingBatch) + + ethereumChainMock.ProposeMultiTransferEsdtBatchCalled = func() { + deposit := deposits[0] + + elrondChainMock.AddDepositToCurrentBatch(deposit) + } + + numRelayers := 3 + relayers := make([]*relay.Relay, 0, numRelayers) + defer func() { + for _, r := range relayers { + _ = r.Close() + } + }() + + messengers := integrationTests.CreateLinkedMessengers(numRelayers) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*1200) + defer cancel() + elrondChainMock.ProcessFinishedHandler = func() { + cancel() + } + + for i := 0; i < numRelayers; i++ { + argsRelay := mock.CreateMockRelayArgs("elrond <-> eth", i, messengers[i], elrondChainMock, ethereumChainMock) + argsRelay.Configs.GeneralConfig.Eth.SafeContractAddress = safeContractEthAddress.Hex() + argsRelay.Erc20Contracts = erc20Contracts + r, err := relay.NewRelay(argsRelay) + require.Nil(t, err) + + elrondChainMock.AddRelayer(r.ElrondAddress()) + ethereumChainMock.AddRelayer(r.EthereumAddress()) + + go func() { + err = r.Start(ctx) + integrationTests.Log.LogIfError(err) + require.Nil(t, err) + }() + + relayers = append(relayers, r) + } + + <-ctx.Done() + + transactions := elrondChainMock.GetAllSentTransactions() + assert.Equal(t, 1, len(transactions)) + assert.Nil(t, elrondChainMock.ProposedTransfer()) + assert.Nil(t, elrondChainMock.PerformedActionID()) + + transfer := ethereumChainMock.GetLastProposedTransfer() + require.NotNil(t, transfer) + + require.Equal(t, numTransactions, len(transfer.Amounts)) + + for i := 0; i < len(transfer.Amounts); i++ { + assert.Equal(t, deposits[i].To, transfer.Recipients[i]) + assert.Equal(t, tokensAddresses[i], transfer.Tokens[i]) + assert.Equal(t, deposits[i].Amount, transfer.Amounts[i]) + } +} + func createTransactions(n int) ([]mock.ElrondDeposit, []common.Address, map[common.Address]*big.Int) { tokensAddresses := make([]common.Address, 0, n) deposits := make([]mock.ElrondDeposit, 0, n) diff --git a/integrationTests/relayers/ethToElrond_test.go b/integrationTests/relayers/ethToElrond_test.go index 0e8bb06d..15f861f8 100644 --- a/integrationTests/relayers/ethToElrond_test.go +++ b/integrationTests/relayers/ethToElrond_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "math/big" + "sync" "testing" "time" @@ -151,6 +152,153 @@ func TestRelayersShouldExecuteTransferFromEthToElrond(t *testing.T) { assert.Equal(t, value2, transfer.Transfers[1].Amount) } +func TestRelayersShouldExecuteTransferFromEthToElrondIfTransactionsAppearInBatch(t *testing.T) { + if testing.Short() { + t.Skip("this is not a short test") + } + + safeContractEthAddress := testsCommon.CreateRandomEthereumAddress() + token1Erc20 := testsCommon.CreateRandomEthereumAddress() + ticker1 := "tck-000001" + + token2Erc20 := testsCommon.CreateRandomEthereumAddress() + ticker2 := "tck-000002" + + value1 := big.NewInt(111111111) + destination1 := testsCommon.CreateRandomElrondAddress() + + value2 := big.NewInt(222222222) + destination2 := testsCommon.CreateRandomElrondAddress() + + tokens := []common.Address{token1Erc20, token2Erc20} + availableBalances := []*big.Int{value1, value2} + + erc20Contracts := make(map[common.Address]eth.Erc20Contract) + for i, token := range tokens { + erc20Contracts[token] = &mockInteractors.Erc20ContractStub{ + BalanceOfCalled: func(ctx context.Context, account common.Address) (*big.Int, error) { + if account == safeContractEthAddress { + return availableBalances[i], nil + } + + return big.NewInt(0), nil + }, + } + } + + batch := contract.Batch{ + Nonce: big.NewInt(1), + Timestamp: big.NewInt(0), + LastUpdatedBlockNumber: big.NewInt(0), + Deposits: []contract.Deposit{ + { + Nonce: big.NewInt(0), + TokenAddress: token1Erc20, + Amount: value1, + Depositor: common.Address{}, + Recipient: core.ConvertFromByteSliceToArray(destination1.AddressBytes()), + Status: 0, + }, + { + Nonce: big.NewInt(0), + TokenAddress: token2Erc20, + Amount: value2, + Depositor: common.Address{}, + Recipient: core.ConvertFromByteSliceToArray(destination2.AddressBytes()), + Status: 0, + }, + }, + } + + ethereumChainMock := mock.NewEthereumChainMock() + ethereumChainMock.SetPendingBatch(batch) + ethereumChainMock.SetQuorum(3) + + elrondChainMock := mock.NewElrondChainMock() + elrondChainMock.AddTokensPair(token1Erc20, ticker1) + elrondChainMock.AddTokensPair(token2Erc20, ticker2) + elrondChainMock.GetStatusesAfterExecutionHandler = func() []byte { + return []byte{bridge.Executed, bridge.Rejected} + } + + newDeposit := contract.Deposit{ + Nonce: big.NewInt(9999), + TokenAddress: token1Erc20, + Amount: big.NewInt(9999), + Depositor: common.Address{}, + Recipient: core.ConvertFromByteSliceToArray(destination1.AddressBytes()), + } + mutFirstTimeProposeCalled := sync.Mutex{} + firstTimeProposeCalled := true + elrondChainMock.ProposeMultiTransferEsdtBatchCalled = func() { + mutFirstTimeProposeCalled.Lock() + defer mutFirstTimeProposeCalled.Unlock() + + if !firstTimeProposeCalled { + return + } + firstTimeProposeCalled = false + + ethereumChainMock.AddDepositToCurrentBatch(newDeposit) + } + + numRelayers := 3 + relayers := make([]*relay.Relay, 0, numRelayers) + defer func() { + for _, r := range relayers { + _ = r.Close() + } + }() + + messengers := integrationTests.CreateLinkedMessengers(numRelayers) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*120) + defer cancel() + ethereumChainMock.ProcessFinishedHandler = func() { + time.Sleep(time.Second * 2) + cancel() + } + + for i := 0; i < numRelayers; i++ { + argsRelay := createMockRelayArgs(i, messengers[i], elrondChainMock, ethereumChainMock) + argsRelay.Configs.GeneralConfig.Eth.SafeContractAddress = safeContractEthAddress.Hex() + argsRelay.Erc20Contracts = erc20Contracts + r, err := relay.NewRelay(argsRelay) + require.Nil(t, err) + + elrondChainMock.AddRelayer(r.ElrondAddress()) + ethereumChainMock.AddRelayer(r.EthereumAddress()) + + go func() { + err = r.Start(ctx) + integrationTests.Log.LogIfError(err) + require.Nil(t, err) + }() + + relayers = append(relayers, r) + } + + <-ctx.Done() + + setStatus := ethereumChainMock.GetLastProposedStatus() + require.NotNil(t, setStatus) + assert.Equal(t, 3, len(setStatus.Signatures)) + assert.Equal(t, []byte{bridge.Executed, bridge.Rejected, bridge.Rejected}, setStatus.NewDepositStatuses) + + assert.NotNil(t, elrondChainMock.PerformedActionID()) + transfer := elrondChainMock.ProposedTransfer() + require.NotNil(t, transfer) + require.Equal(t, 2, len(transfer.Transfers)) + + assert.Equal(t, destination1.AddressBytes(), transfer.Transfers[0].To) + assert.Equal(t, hex.EncodeToString([]byte(ticker1)), transfer.Transfers[0].Token) + assert.Equal(t, value1, transfer.Transfers[0].Amount) + + assert.Equal(t, destination2.AddressBytes(), transfer.Transfers[1].To) + assert.Equal(t, hex.EncodeToString([]byte(ticker2)), transfer.Transfers[1].Token) + assert.Equal(t, value2, transfer.Transfers[1].Amount) +} + func createMockRelayArgs( index int, messenger p2p.Messenger, diff --git a/integrationTests/testscommon.go b/integrationTests/testscommon.go index 2562b992..8a44638c 100644 --- a/integrationTests/testscommon.go +++ b/integrationTests/testscommon.go @@ -34,7 +34,7 @@ var TestMarshalizer = &marshal.JsonMarshalizer{} // TestHasher - var TestHasher = blake2b.NewBlake2b() -// EthNoPendingBatch - +// NoPendingBatch - var NoPendingBatch = contract.Batch{ Nonce: big.NewInt(0), } diff --git a/testsCommon/bridgeMock.go b/testsCommon/bridgeMock.go index 1954cb4e..30a875a6 100644 --- a/testsCommon/bridgeMock.go +++ b/testsCommon/bridgeMock.go @@ -172,7 +172,7 @@ func (bm *BridgeMock) GetExecuted() (bridge.ActionId, bridge.BatchId) { } // SignersCount - -func (bm *BridgeMock) SignersCount(_ *bridge.Batch, id bridge.ActionId, _ bridge.SignaturesHolder) uint { +func (bm *BridgeMock) SignersCount(_ context.Context, _ *bridge.Batch, id bridge.ActionId, _ bridge.SignaturesHolder) uint { bm.RLock() defer bm.RUnlock() diff --git a/testsCommon/bridgeStub.go b/testsCommon/bridgeStub.go index 2aa0a032..a0368eb5 100644 --- a/testsCommon/bridgeStub.go +++ b/testsCommon/bridgeStub.go @@ -27,7 +27,7 @@ type BridgeStub struct { GetActionIdForSetStatusOnPendingTransferCalled func(ctx context.Context, batch *bridge.Batch) bridge.ActionId SignCalled func(ctx context.Context, id bridge.ActionId) (string, error) ExecuteCalled func(ctx context.Context, id bridge.ActionId, batch *bridge.Batch, sigHolder bridge.SignaturesHolder) (string, error) - SignersCountCalled func(batch *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint + SignersCountCalled func(ctx context.Context, batch *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint GetTransactionsStatusesCalled func(ctx context.Context, batchID bridge.BatchId) ([]uint8, error) ProposeTransferError error @@ -135,10 +135,10 @@ func (b *BridgeStub) Execute(ctx context.Context, id bridge.ActionId, batch *bri } // SignersCount - -func (b *BridgeStub) SignersCount(batch *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { +func (b *BridgeStub) SignersCount(ctx context.Context, batch *bridge.Batch, id bridge.ActionId, sigHolder bridge.SignaturesHolder) uint { b.incrementFunctionCounter() if b.SignersCountCalled != nil { - return b.SignersCountCalled(batch, id, sigHolder) + return b.SignersCountCalled(ctx, batch, id, sigHolder) } return 0 } diff --git a/testsCommon/interactors/ethereumChainInteractorStub.go b/testsCommon/interactors/ethereumChainInteractorStub.go index 2c5c32b0..674d1a75 100644 --- a/testsCommon/interactors/ethereumChainInteractorStub.go +++ b/testsCommon/interactors/ethereumChainInteractorStub.go @@ -35,7 +35,9 @@ func (stub *EthereumChainInteractorStub) GetNextPendingBatch(ctx context.Context return stub.GetNextPendingBatchCalled(ctx) } - return contract.Batch{}, nil + return contract.Batch{ + Nonce: big.NewInt(1), + }, nil } // WasBatchExecuted -