diff --git a/README.md b/README.md index 18a84b4..e4b68e4 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ for detailed explanation and flags of each command see help | db.badger.core.scanRangeThreshold | BADGER_CORE_SCANRANGETHRESHOLD | `2000` | ❌ | max allowed block range for log requests | | db.badger.core.maxScanIterators | BADGER_CORE_MAXSCANITERATORS | `10000` | ❌ | max logs returned if the scanRangeThreshold exceeded | | db.badger.core.filterTtlMinutes | BADGER_CORE_FILTERTTLMINUTES | `15` | ❌ | retention time for stored filters (not used) | -| db.badger.core.options | | | | see [BadgerDB] for more options | +| db.badger.core.options | | | | see [BadgerDB] for more options | | db.badger.core.options.Dir | BADGER_CORE_OPTIONS_DIR | `/tmp/relayer/data` | ❌ | see [BadgerDB] for details | | db.badger.core.options.ValueDir | BADGER_CORE_OPTIONS_VALUEDIR | `/tmp/relayer/data` | ❌ | see [BadgerDB] for details | | db.badger.core.options.InMemory | BADGER_CORE_OPTIONS_INMEMORY | `false` | ❌ | see [BadgerDB] for details | @@ -96,6 +96,8 @@ for detailed explanation and flags of each command see help | endpoint.engine.nearNodeURL | ENDPOINT_ENGINE_NEARNODEURL | user should specify :heavy_exclamation_mark: | ❌ | URL of a Near Node to communicate | | endpoint.engine.signer | ENDPOINT_ENGINE_SIGNER | user should specify :heavy_exclamation_mark: | ❌ | to be able to communicate with Near Node, user must have a Near account | | endpoint.engine.signerKey | ENDPOINT_ENGINE_SIGNERKEY | user should specify :heavy_exclamation_mark: | ❌ | path to JSON file containing the Near Credentials. To be able to communicate with Near Node, user must have a Near signer key associated to account | +| endpoint.engine.functionKeyPrefixPattern | ENDPOINT_ENGINE_FUNCTIONKEYPREFIXPATTERN | `fk*.` | ❌ | prefix pattern to search for key pair files of Function Call Keys | +| endpoint.engine.functionKeyMapper | ENDPOINT_ENGINE_FUNCTIONKEYMAPPER | `CRC32` | ❌ | hashing algorithm used to map Eth senders to Function Call Keys | | endpoint.engine.asyncSendRawTxs | ENDPOINT_ENGINE_ASYNCSENDRAWTXS | `true` | ❌ | if true, transaction calls to Near Node are made in async. fashion | | endpoint.engine.minGasPrice | ENDPOINT_ENGINE_MINGASPRICE | `0` | ❌ | | | endpoint.engine.minGasLimit | ENDPOINT_ENGINE_MINGASLIMIT | `21000` | ❌ | | diff --git a/config/mainnet.yaml b/config/mainnet.yaml index 901e551..f44592b 100644 --- a/config/mainnet.yaml +++ b/config/mainnet.yaml @@ -23,6 +23,8 @@ endpoint: nearNodeURL: https://archival-rpc.mainnet.near.org signer: signerKey: + functionKeyPrefixPattern: "fk*." + functionKeyMapper: "CRC32" # can take values; CRC32, RoundRobin. Default CRC32 asyncSendRawTxs: true minGasPrice: 0 minGasLimit: 21000 diff --git a/config/testnet.yaml b/config/testnet.yaml index 4f05eb2..a026864 100644 --- a/config/testnet.yaml +++ b/config/testnet.yaml @@ -23,6 +23,8 @@ endpoint: nearNodeURL: https://archival-rpc.testnet.near.org signer: signerKey: + functionKeyPrefixPattern: "fk*." + functionKeyMapper: "CRC32" # can take values; CRC32, RoundRobin. Default CRC32 asyncSendRawTxs: false minGasPrice: 0 minGasLimit: 21000 diff --git a/endpoint/engineEth.go b/endpoint/engineEth.go index cd85ded..82c474f 100644 --- a/endpoint/engineEth.go +++ b/endpoint/engineEth.go @@ -4,7 +4,9 @@ import ( "context" "errors" "fmt" + "hash/crc32" "strings" + "sync/atomic" "time" "github.com/aurora-is-near/near-api-go" @@ -19,12 +21,42 @@ import ( var ( beforeGenesisError = "DB Not Found Error" beforeAuroraError = "does not exist while viewing" - tooManyrequestsError = "429" + tooManyRequestsError = "429" ) +type SenderKeyMapper interface { + Map(sender string, totalNumKeys int) (keyIndex int) +} + +// CRC32Mapper is a signer to key mapper implementation that uses crc32 hash to consistently distributes signers to keys. +// This ensures that same signers are mapped to same key +type CRC32Mapper struct{} + +func (m CRC32Mapper) Map(sender string, totalNumKeys int) int { + idx := crc32.ChecksumIEEE([]byte(sender)) % uint32(totalNumKeys) + return int(idx) +} + +// RoundRobinMapper is a signer to key mapper implementation that equally distributes signers to keys +type RoundRobinMapper struct { + offset uint32 +} + +func (m RoundRobinMapper) Map(sender string, totalNumKeys int) int { + return m.rrMap(totalNumKeys) +} + +func (m RoundRobinMapper) rrMap(totalNumKeys int) int { + offset := atomic.AddUint32(&m.offset, 1) - 1 + idx := offset % uint32(totalNumKeys) + return int(idx) +} + type EngineEth struct { *endpoint.Endpoint signer *near.Account + keys []string + mapper SenderKeyMapper } func NewEngineEth(ep *endpoint.Endpoint) *EngineEth { @@ -34,30 +66,42 @@ func NewEngineEth(ep *endpoint.Endpoint) *EngineEth { // Establish engine communication and auth the near account nearCfg := &near.Config{ - NetworkID: ep.Config.EngineConfig.NearNetworkID, - NodeURL: ep.Config.EngineConfig.NearNodeURL, + NetworkID: ep.Config.EngineConfig.NearNetworkID, + NodeURL: ep.Config.EngineConfig.NearNodeURL, + FunctionKeyPrefixPattern: ep.Config.EngineConfig.FunctionKeyPrefixPattern, } if ep.Config.EngineConfig.SignerKey != "" { nearCfg.KeyPath = ep.Config.EngineConfig.SignerKey } - nearcon := near.NewConnection(nearCfg.NodeURL) - nearaccount, err := near.LoadAccount(nearcon, nearCfg, ep.Config.EngineConfig.Signer) + nearConn := near.NewConnection(nearCfg.NodeURL) + nearAccount, err := near.LoadAccount(nearConn, nearCfg, ep.Config.EngineConfig.Signer) if err != nil { ep.Logger.Panic().Err(err).Msg("failed to load Near account") } + var mapper SenderKeyMapper + switch ep.Config.EngineConfig.FunctionKeyMapper { + case "CRC32": + mapper = CRC32Mapper{} + case "RoundRobin": + mapper = RoundRobinMapper{} + } + eEth := &EngineEth{ Endpoint: ep, - signer: nearaccount, + signer: nearAccount, + keys: nearAccount.GetVerifiedAccessKeys(), + mapper: mapper, } + return eEth } // ChainId returns the chain id of the current network // -// On failure to access engine or format error on the response, returns error code '-32000' with custom message. -// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. -// On any param returns error code '-32602' with custom message. +// On failure to access engine or format error on the response, returns error code '-32000' with custom message. +// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. +// On any param returns error code '-32602' with custom message. func (e *EngineEth) ChainId(ctx context.Context) (*common.Uint256, error) { return endpoint.Process(ctx, "eth_chainId", e.Endpoint, func(ctx context.Context) (*common.Uint256, error) { return e.chainId(ctx) @@ -74,9 +118,9 @@ func (e *EngineEth) chainId(_ context.Context) (*common.Uint256, error) { // GetCode returns the compiled smart contract code, if any, at a given address // -// On failure to access engine or format error on the response, returns error code '-32000' with custom message. -// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. -// On missing or invalid param returns error code '-32602' with custom message. +// On failure to access engine or format error on the response, returns error code '-32000' with custom message. +// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. +// On missing or invalid param returns error code '-32602' with custom message. func (e *EngineEth) GetCode(ctx context.Context, address common.Address, bNumOrHash *common.BlockNumberOrHash) (*string, error) { return endpoint.Process(ctx, "eth_getCode", e.Endpoint, func(ctx context.Context) (*string, error) { return e.getCode(ctx, address, bNumOrHash) @@ -102,9 +146,9 @@ func (e *EngineEth) getCode(ctx context.Context, address common.Address, bNumOrH // GetBalance returns the balance of the account of given address // -// On failure to access engine or format error on the response, returns error code '-32000' with custom message. -// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. -// On missing or invalid param returns error code '-32602' with custom message. +// On failure to access engine or format error on the response, returns error code '-32000' with custom message. +// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. +// On missing or invalid param returns error code '-32602' with custom message. func (e *EngineEth) GetBalance(ctx context.Context, address common.Address, bNumOrHash *common.BlockNumberOrHash) (*common.Uint256, error) { return endpoint.Process(ctx, "eth_getBalance", e.Endpoint, func(ctx context.Context) (*common.Uint256, error) { return e.getBalance(ctx, address, bNumOrHash) @@ -130,9 +174,9 @@ func (e *EngineEth) getBalance(ctx context.Context, address common.Address, bNum // GetTransactionCount returns the number of transactions sent from an address // -// On failure to access engine or format error on the response, returns error code '-32000' with custom message. -// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. -// On missing or invalid param returns error code '-32602' with custom message. +// On failure to access engine or format error on the response, returns error code '-32000' with custom message. +// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. +// On missing or invalid param returns error code '-32602' with custom message. func (e *EngineEth) GetTransactionCount(ctx context.Context, address common.Address, bNumOrHash *common.BlockNumberOrHash) (*common.Uint256, error) { return endpoint.Process(ctx, "eth_getTransactionCount", e.Endpoint, func(ctx context.Context) (*common.Uint256, error) { return e.getTransactionCount(ctx, address, bNumOrHash) @@ -150,7 +194,7 @@ func (e *EngineEth) getTransactionCount(ctx context.Context, address common.Addr // Return "0x0" for the blocks before Aurora account or before Genesis if strings.Contains(err.Error(), beforeAuroraError) || strings.Contains(err.Error(), beforeGenesisError) { return utils.Constants.ZeroUint256(), nil - } else if strings.Contains(err.Error(), tooManyrequestsError) { + } else if strings.Contains(err.Error(), tooManyRequestsError) { return nil, errors.New("engine error 429, too many requests received") } return nil, &errs.GenericError{Err: err} @@ -160,9 +204,9 @@ func (e *EngineEth) getTransactionCount(ctx context.Context, address common.Addr // GetStorageAt returns the value from a storage position at a given address // -// On failure to access engine or format error on the response, returns error code '-32000' with custom message. -// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. -// On missing or invalid param returns error code '-32602' with custom message. +// On failure to access engine or format error on the response, returns error code '-32000' with custom message. +// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. +// On missing or invalid param returns error code '-32602' with custom message. func (e *EngineEth) GetStorageAt(ctx context.Context, address common.Address, storageSlot common.Uint256, bNumOrHash *common.BlockNumberOrHash) (*string, error) { return endpoint.Process(ctx, "eth_getStorageAt", e.Endpoint, func(ctx context.Context) (*string, error) { return e.getStorageAt(ctx, address, storageSlot, bNumOrHash) @@ -192,9 +236,9 @@ func (e *EngineEth) getStorageAt(ctx context.Context, address common.Address, st // Call executes a new message call immediately without creating a transaction on the blockchain // -// On failure to access engine or format error on the response, returns error code '-32000' with custom message. -// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. -// On missing or invalid param returns error code '-32602' with custom message. +// On failure to access engine or format error on the response, returns error code '-32000' with custom message. +// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. +// On missing or invalid param returns error code '-32602' with custom message. func (e *EngineEth) Call(ctx context.Context, txs engine.TransactionForCall, bNumOrHash *common.BlockNumberOrHash) (*string, error) { return endpoint.Process(ctx, "eth_call", e.Endpoint, func(ctx context.Context) (*string, error) { return e.call(ctx, txs, bNumOrHash) @@ -224,9 +268,9 @@ func (e *EngineEth) call(ctx context.Context, txs engine.TransactionForCall, bNu // SendRawTransaction submits a raw transaction to engine either asynchronously or synchronously based on the configuration // -// On failure to access engine or format error on the response, returns error code '-32000' with custom message. -// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. -// On missing or invalid param returns error code '-32602' with custom message. +// On failure to access engine or format error on the response, returns error code '-32000' with custom message. +// If API is disabled, returns error code '-32601' with message 'the method does not exist/is not available'. +// On missing or invalid param returns error code '-32602' with custom message. func (e *EngineEth) SendRawTransaction(ctx context.Context, txs common.DataVec) (*string, error) { return endpoint.Process(ctx, "eth_sendRawTransaction", e.Endpoint, func(ctx context.Context) (*string, error) { return e.sendRawTransaction(ctx, txs) @@ -234,22 +278,44 @@ func (e *EngineEth) SendRawTransaction(ctx context.Context, txs common.DataVec) } func (e *EngineEth) sendRawTransaction(_ context.Context, txs common.DataVec) (*string, error) { + + // check transaction data and return error if any issues (like low gas price or gas limit) + sender, err := validateRawTransaction(txs.Bytes(), e.Config.EngineConfig) + if err != nil { + return nil, &errs.InvalidParamsError{Message: err.Error()} + } + + key := e.keys[e.mapper.Map(sender.Hex(), len(e.keys))] + e.Endpoint.Logger.Debug().Msgf("sender: [%s], key: [%s]", sender.Hex(), key) + // Call either async or sync version of sendRawTransaction according to the configuration parameter if e.Config.EngineConfig.AsyncSendRawTxs { - return e.asyncSendRawTransaction(txs.Bytes()) + return e.asyncSendRawTransactionWithPublicKey(txs.Bytes(), key) } else { - return e.syncSendRawTransaction(txs.Bytes()) + return e.syncSendRawTransactionWithPublicKey(txs.Bytes(), key) } } // asyncSendRawTransaction submits a raw transaction to engine asynchronously +// +// Deprecated: Use asyncSendRawTransactionWithPublicKey instead func (e *EngineEth) asyncSendRawTransaction(txsBytes []byte) (*string, error) { - // check transaction data and return error if any issues (like low gas price or gas limit) - err := validateRawTransaction(txsBytes, e.Config.EngineConfig) + + resp, err := e.sendRawTransactionWithRetry(txsBytes) if err != nil { - return nil, &errs.InvalidParamsError{Message: err.Error()} + return nil, &errs.GenericError{Err: err} } - resp, err := e.sendRawTransactionWithRetry(txsBytes) + txsHash := utils.CalculateKeccak256(txsBytes) + e.Logger.Info().Msgf("Near txs hash is: %s, for Eth txs hash: %s", *resp, txsHash) + + return &txsHash, nil +} + +// asyncSendRawTransactionWithPublicKey submits a raw transaction to engine asynchronously. Transaction is signed with the key pair defined +// by the publicKey arg +func (e *EngineEth) asyncSendRawTransactionWithPublicKey(txsBytes []byte, publicKey string) (*string, error) { + + resp, err := e.sendRawTransactionWithRetryWithPublicKey(txsBytes, publicKey) if err != nil { return nil, &errs.GenericError{Err: err} } @@ -260,16 +326,26 @@ func (e *EngineEth) asyncSendRawTransaction(txsBytes []byte) (*string, error) { } // syncSendRawTransaction submits a raw transaction to engine synchronously +// +// Deprecated: Use syncSendRawTransactionWithPublicKey instead func (e *EngineEth) syncSendRawTransaction(txsBytes []byte) (*string, error) { - // check transaction data and return error if any issues (like low gas price or gas limit) - err := validateRawTransaction(txsBytes, e.Config.EngineConfig) + + txsHash := utils.CalculateKeccak256(txsBytes) + amount := e.Config.EngineConfig.DepositForNearTxsCall + resp, err := e.signer.FunctionCall(utils.AccountId, "submit", txsBytes, e.Config.EngineConfig.GasForNearTxsCall, *amount) if err != nil { - return nil, &errs.InvalidParamsError{Message: err.Error()} + return nil, &errs.GenericError{Err: err} } + return getTxsResultFromEngineResponse(resp, txsHash) +} + +// syncSendRawTransactionWithPublicKey submits a raw transaction to engine synchronously. Transaction is signed with the key pair defined +// by the publicKey arg +func (e *EngineEth) syncSendRawTransactionWithPublicKey(txsBytes []byte, publicKey string) (*string, error) { txsHash := utils.CalculateKeccak256(txsBytes) amount := e.Config.EngineConfig.DepositForNearTxsCall - resp, err := e.signer.FunctionCall(utils.AccountId, "submit", txsBytes, e.Config.EngineConfig.GasForNearTxsCall, *amount) + resp, err := e.signer.FunctionCallWithMultiActionAndKey(utils.AccountId, "submit", publicKey, [][]byte{txsBytes}, e.Config.EngineConfig.GasForNearTxsCall, *amount) if err != nil { return nil, &errs.GenericError{Err: err} } @@ -296,6 +372,26 @@ func (e *EngineEth) sendRawTransactionWithRetry(txsBytes []byte) (*string, error return nil, errors.New("sendRawTransaction: maximum retries reached") } +// sendRawTransactionWithRetryWithPublicKey send the Txs with a constant configurable retry count and duration in case of error +func (e *EngineEth) sendRawTransactionWithRetryWithPublicKey(txsBytes []byte, publicKey string) (*string, error) { + amount := e.Config.EngineConfig.DepositForNearTxsCall + gas := e.Config.EngineConfig.GasForNearTxsCall + waitTimeMs := e.Config.EngineConfig.RetryWaitTimeMsForNearTxsCall + retryNumber := e.Config.EngineConfig.RetryNumberForNearTxsCall + + for i := 0; i < retryNumber; i++ { + resp, err := e.signer.FunctionCallAsyncWithMultiActionAndKey(utils.AccountId, "submit", publicKey, [][]byte{txsBytes}, gas, *amount) + if err == nil { + return &resp, nil + } + if i < retryNumber-1 { + e.Logger.Error().Msgf("sendRawTxs error on iteration %d: %s", i, err.Error()) + time.Sleep(time.Duration(waitTimeMs) * time.Millisecond) + } + } + return nil, errors.New("sendRawTransaction: maximum retries reached") +} + func (e *EngineEth) blockNumberByNumberOrHash(ctx context.Context, bNumOrHash *common.BlockNumberOrHash) (*common.BN64, error) { if bNumOrHash == nil { bn := common.LatestBlockNumber @@ -378,25 +474,28 @@ func getTxsResultFromEngineResponse(respArg interface{}, txsHash string) (*strin } // validateRawTransaction validates the raw transaction by checking GasPrice and Gas Limit -func validateRawTransaction(rawRxsBytes []byte, cfg endpoint.EngineConfig) error { +func validateRawTransaction(rawRxsBytes []byte, cfg endpoint.EngineConfig) (*common.Address, error) { txsObj, err := parseTransactionFromBinary(rawRxsBytes) if err != nil { - return err + return nil, err } // check if provided sender address is valid - if _, err := extractTransactionSender(txsObj); err != nil { - return fmt.Errorf("can't extract transaction sender: %v", err) + sender, err := extractTransactionSender(txsObj) + if err != nil { + return nil, fmt.Errorf("can't extract transaction sender: %v", err) } + // check if provided gas price is bigger than min gas price limit if txsObj.GasPrice().Cmp(cfg.MinGasPrice) < 0 { - return errors.New("gas price too low") + return nil, errors.New("gas price too low") } + // check if provided gas limit is bigger than min gas limit if txsObj.Gas() < cfg.MinGasLimit { - return errors.New("intrinsic gas too low") + return nil, errors.New("intrinsic gas too low") } - return nil + return sender, nil } // parseTransactionFromBinary decodes the sendRawTransaction data to a go-ethereum transaction structure @@ -409,7 +508,7 @@ func parseTransactionFromBinary(txBinary []byte) (*gethtypes.Transaction, error) return &tx, nil } -// extractTransactionSender decodes the sendRawTransaction sender data and returns if the operation failse +// extractTransactionSender decodes the sendRawTransaction sender data and returns if the operation false func extractTransactionSender(tx *gethtypes.Transaction) (*common.Address, error) { addr, err := gethtypes.Sender(gethtypes.LatestSignerForChainID(tx.ChainId()), tx) if err != nil { diff --git a/go.mod b/go.mod index e5d3be0..6bbe2e5 100644 --- a/go.mod +++ b/go.mod @@ -2,15 +2,15 @@ module github.com/aurora-is-near/relayer2-public go 1.18 -// replace github.com/aurora-is-near/relayer2-base => ../relayer2-base +//replace github.com/aurora-is-near/relayer2-base => ../relayer2-base // The following package had a conflicting dependency. // Fixed by pointing the dependency to the latest version tag. replace github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.23.2 require ( - github.com/aurora-is-near/near-api-go v0.0.13-0.20230718083121-2b3b0cdd7ac4 - github.com/aurora-is-near/relayer2-base v1.1.1 + github.com/aurora-is-near/near-api-go v0.0.13-0.20230808115242-54ddcda150c6 + github.com/aurora-is-near/relayer2-base v1.1.2 github.com/ethereum/go-ethereum v1.10.25 github.com/json-iterator/go v1.1.12 github.com/spf13/cobra v1.6.0 diff --git a/go.sum b/go.sum index 09d520f..014fe1c 100644 --- a/go.sum +++ b/go.sum @@ -52,10 +52,12 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aurora-is-near/go-jsonrpc/v3 v3.1.1 h1:Nw9J9K7CksfVBa9uCVfvf1uAIQRhrNG677q8eH1gtVg= github.com/aurora-is-near/go-jsonrpc/v3 v3.1.1/go.mod h1:Li013EFlPu3crtlFQtWJAeE7VmdhSsxOpRoop1J0icw= -github.com/aurora-is-near/near-api-go v0.0.13-0.20230718083121-2b3b0cdd7ac4 h1:CSR9l311tsTj8EgQ/jqzLod7u7V1tq3HDnRRKZrNNGY= -github.com/aurora-is-near/near-api-go v0.0.13-0.20230718083121-2b3b0cdd7ac4/go.mod h1:u1KDIqjCl+g+Ndp8izReS5zXI05YWCgQR3iOYXhnhHI= -github.com/aurora-is-near/relayer2-base v1.1.1 h1:ZmZt23Il6417rsk+eS07imex1LStI7qQq7rWpcT9Pcs= -github.com/aurora-is-near/relayer2-base v1.1.1/go.mod h1:kvEh47ywen00njN0XWPWj2RDGa1CXDF13EjDjLiRojo= +github.com/aurora-is-near/near-api-go v0.0.13-0.20230808115242-54ddcda150c6 h1:PaHSXsJp0P4vT/EN4XJdsvOMYXeQLW9fEO9wle8FEVc= +github.com/aurora-is-near/near-api-go v0.0.13-0.20230808115242-54ddcda150c6/go.mod h1:u1KDIqjCl+g+Ndp8izReS5zXI05YWCgQR3iOYXhnhHI= +github.com/aurora-is-near/relayer2-base v1.1.2-0.20230804133711-8cee7c9a5993 h1:w0YZl8uI3s4w6QKD6vEJ1R/u0zQlQfQbL8PM14FXjek= +github.com/aurora-is-near/relayer2-base v1.1.2-0.20230804133711-8cee7c9a5993/go.mod h1:kvEh47ywen00njN0XWPWj2RDGa1CXDF13EjDjLiRojo= +github.com/aurora-is-near/relayer2-base v1.1.2 h1:bmbB8SspwTXmAQFXzgNP1LTXe+QVjSlzV2bA0/Yz5Vw= +github.com/aurora-is-near/relayer2-base v1.1.2/go.mod h1:kvEh47ywen00njN0XWPWj2RDGa1CXDF13EjDjLiRojo= github.com/aurora-is-near/stream-backup v0.0.0-20221212013533-1e06e263c3f7 h1:aHMsjwM2KJ6EO9H7f1UgFD95c+d9BTpA43NQgZ6hiaM= github.com/aurora-is-near/stream-backup v0.0.0-20221212013533-1e06e263c3f7/go.mod h1:71KeQcNFeKIHH0WeppWnXZBfwpGo/YwLK8TtC+r+TfY= github.com/btcsuite/btcd v0.23.2 h1:/YOgUp25sdCnP5ho6Hl3s0E438zlX+Kak7E6TgBgoT0=