diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 79d31f32..b4edb3fa 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -1,7 +1,7 @@ version: "3" services: federation: - image: sovryn/fed-tokenbridge:10.19 + image: sovryn/fed-tokenbridge:10.19.1 ports: - 4444:30303 volumes: diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 65bf2cac..ef120729 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -1,7 +1,7 @@ version: "3" services: federation: - image: sovryn/fed-tokenbridge:10.19 + image: sovryn/fed-tokenbridge:10.19.1 ports: - 30303:30303 volumes: diff --git a/federator-env/mainnet-BSC-RSK/bmainnet.json b/federator-env/mainnet-BSC-RSK/bmainnet.json index e9ef6e83..7f035905 100644 --- a/federator-env/mainnet-BSC-RSK/bmainnet.json +++ b/federator-env/mainnet-BSC-RSK/bmainnet.json @@ -5,5 +5,5 @@ "allowTokens": "0xc4b5178Cc086E764568AdfB2dacCBB0d973e8132", "erc777Converter": "0x9D46B33171eA7124aEE472bFe61B5B7084B55069", "host": "https://bsc.sovryn.app/mainnet", - "fromBlock": 21118895 + "fromBlock": 21892594 } diff --git a/federator-env/mainnet-BSC-RSK/rskmainnet.json b/federator-env/mainnet-BSC-RSK/rskmainnet.json index 5a3fd62f..e111b43a 100644 --- a/federator-env/mainnet-BSC-RSK/rskmainnet.json +++ b/federator-env/mainnet-BSC-RSK/rskmainnet.json @@ -5,5 +5,5 @@ "allowTokens": "0x200FD7a1cCEa4651F15008cC99bF82d7461EFd3F", "erc777Converter": "0xc8149b1F15794D135Dfe2924955cb90709Ac7070", "host": "https://mainnet.sovryn.app/rpc", - "fromBlock": 4614985 + "fromBlock": 4690509 } diff --git a/federator-env/mainnet-ETH-RSK/mainnet.json b/federator-env/mainnet-ETH-RSK/mainnet.json index 75cbaa2d..40eff6e1 100644 --- a/federator-env/mainnet-ETH-RSK/mainnet.json +++ b/federator-env/mainnet-ETH-RSK/mainnet.json @@ -5,5 +5,5 @@ "allowTokens": "0xf9A59a649859A27d664C8bDb51fA53bCb268545C", "erc777Converter": "0xC0b2A9E31f69e4F0bC24584C678C582714a4fA1b", "host": "https://mainnet.infura.io/v3/f02caaf003d14ad7bee83f33eeda2f5a", - "fromBlock": 15483239 + "fromBlock": 15674601 } diff --git a/federator-env/mainnet-ETH-RSK/rskmainnet.json b/federator-env/mainnet-ETH-RSK/rskmainnet.json index cbe6762c..50c42b48 100644 --- a/federator-env/mainnet-ETH-RSK/rskmainnet.json +++ b/federator-env/mainnet-ETH-RSK/rskmainnet.json @@ -5,5 +5,5 @@ "allowTokens": "0x7DC1D73C620cF8eB167eDD32942DA0d01B70adC0", "erc777Converter": "0xb86623c103843ccf75c6f0073d84bcfc0e34536c", "host": "https://mainnet.sovryn.app/rpc", - "fromBlock": 4615325 + "fromBlock": 4690549 } \ No newline at end of file diff --git a/sovryn-token-bridge/federator/src/lib/Federator.js b/sovryn-token-bridge/federator/src/lib/Federator.js index 25abc3d6..eaf1b2d3 100644 --- a/sovryn-token-bridge/federator/src/lib/Federator.js +++ b/sovryn-token-bridge/federator/src/lib/Federator.js @@ -50,6 +50,9 @@ module.exports = class Federator { this.confirmationTable = config.confirmationTable; this.chatBot = chatBot || new NullBot(this.logger); + + this.numBlacklisted = 0; + this.numValid = 0; } async populateMemberAddresses() { @@ -150,6 +153,13 @@ module.exports = class Federator { let newLastBlockNumber; let allLogsConfirmed = true; for (let log of logs) { + if (await this._isBlackListedLog(log)) { + this.numBlacklisted++; + this.logger.warn(`BLACKLISTED_TRANSFER in tx: ${log.transactionHash}, num_bl: ${this.numBlacklisted}, num_valid: ${this.numValid}`); + this.logger.warn('Skipping transfer to/from a blacklisted address, skipped log:'); + continue; + } + this.logger.info('Processing event log:', log); const { _amount: amount, _symbol: symbol } = log.returnValues; @@ -161,9 +171,11 @@ module.exports = class Federator { `Block: ${log.blockHash} Tx: ${log.transactionHash} token: ${symbol} is already processed (on Bridge) -- no need to get signatures` ); } else { - const signatures = await this._requestSignatureFromFederators(log); - this.logger.info('Collected enough signatures'); - await this._processLog(log, signatures); + this.numValid++; + this.logger.info(`VALID_TRANSFER in tx: ${log.transactionHash}, num_bl: ${this.numBlacklisted}, num_valid: ${this.numValid}`); + // const signatures = await this._requestSignatureFromFederators(log); + // this.logger.info('Collected enough signatures'); + // await this._processLog(log, signatures); } } else if (allLogsConfirmed) { newLastBlockNumber = log.blockNumber - 1; @@ -178,6 +190,7 @@ module.exports = class Federator { } async _requestSignatureFromFederators(log) { + return []; return new Promise((resolve, reject) => { this.logger.info('Requesting other federators to sign event'); @@ -289,6 +302,12 @@ module.exports = class Federator { } async _isAlreadyProcessed(log) { + const isBlacklisted = await this._isBlackListedLog(log); // extra safety + if (isBlacklisted) { + this.logger.warn('Treating already processed log as blacklisted. Log:'); + this.logger.warn(log); + return true; + } const { _to: receiver, _amount: amount, @@ -426,6 +445,8 @@ module.exports = class Federator { transactionIdU, signatures ) { + this.logger.error("EXECUTION DISABLED"); + return; signatures = signatures && this._removeNotNeededSignatures(signatures); try { @@ -597,6 +618,13 @@ module.exports = class Federator { throw new CustomError('Invalid return when searching for event'); } + if (await this._isBlackListedLog(logs[0])) { + throw new CustomError(`Refusing to sign blacklisted log ${logs[0].id}, tx ${logs[0].transactionHash}`); + } + + this.logger.info("Would sign valid tx but that is currently disabled"); + return; + const { _tokenAddress, _amount, _to, _symbol, _decimals, _granularity, _userData } = logs[0].returnValues; const { blockHash, transactionHash, logIndex } = logs[0]; @@ -636,4 +664,44 @@ module.exports = class Federator { return { signatureData: { signature, deadline }, logId: id }; } + + _isBlackListedAddress(address) { + if (typeof address !== 'string' || !address.startsWith('0x') || address.length !== 42) { + throw new CustomError(`Invalid address: ${address}`); + } + return ( + address.toLowerCase() === + '0xc92ebecda030234c10e149beead6bba61197531a'.toLowerCase() + ); + } + + async _isBlackListedLog(log) { + const txHash = log.transactionHash; + const receiver = log.returnValues._to; + const userData = log.returnValues._userData; + if (this._isBlackListedAddress(receiver)) { + this.logger.warn(`Transaction ${txHash} is blacklisted (receiver ${receiver})`); + return true; + } + if (userData) { + if (userData.length === 66) { + const userDataAddress = userData.replace(/^0x000000000000000000000000/, '0x') + if (userDataAddress.length === 42 && this._isBlackListedAddress(userDataAddress)) { + this.logger.warn(`Transaction ${txHash} is blacklisted (userData ${userData})`); + return true; + } + } else if (userData.length > 66) { + this.logger.warn(`Transaction ${txHash} is a direct fastbtc transfer which is currently blocked`); + return true; + } + } + + const transaction = await this.mainWeb3.eth.getTransaction(txHash); + if (this._isBlackListedAddress(transaction.from)) { + this.logger.warn(`Transaction ${txHash} is blacklisted (sender ${transaction.from})`); + return true; + } + + return false; + } }; diff --git a/sovryn-token-bridge/federator/src/lib/GasServices.js b/sovryn-token-bridge/federator/src/lib/GasServices.js index 6550492b..7598c88d 100644 --- a/sovryn-token-bridge/federator/src/lib/GasServices.js +++ b/sovryn-token-bridge/federator/src/lib/GasServices.js @@ -74,6 +74,8 @@ module.exports = class GasServices { this.logger.error('Unhandled Error on gasPriceAvg start()', err); }); + console.log("Blacklist test neabled, not waiting for average gas."); + return; this.logger.info( `Process is waiting to calculate average gas of ${avgGasPeriodInterval} ms.` ); diff --git a/sovryn-token-bridge/federator/src/main.js b/sovryn-token-bridge/federator/src/main.js index 64abd126..52fbdd98 100644 --- a/sovryn-token-bridge/federator/src/main.js +++ b/sovryn-token-bridge/federator/src/main.js @@ -202,10 +202,11 @@ async function run() { } catch (e) { // just ignore now, this is only for debugging } - if (numOtherPeers < config.minimumPeerAmount) { - logger.info(`Waiting for enough peers (now ${numOtherPeers}. node ${nodeId}, leader ${leaderId})`); - return; - } + console.log("Blacklist test neabled, not waiting for peers.") + // if (numOtherPeers < config.minimumPeerAmount) { + // logger.info(`Waiting for enough peers (now ${numOtherPeers}. node ${nodeId}, leader ${leaderId})`); + // return; + // } if (p2pNode.isLeader()) { console.log(`This node is a leader -- handling iteration. (${numOtherPeers} other peers, node ${nodeId}, leader ${leaderId})`); diff --git a/sovryn-token-bridge/federator/test/Federator.test.js b/sovryn-token-bridge/federator/test/Federator.test.js index 59ebea53..17473201 100644 --- a/sovryn-token-bridge/federator/test/Federator.test.js +++ b/sovryn-token-bridge/federator/test/Federator.test.js @@ -1108,4 +1108,182 @@ describe('Federator module tests', () => { expect(sendTransactionSpy).toHaveBeenCalledTimes(2); }); }); + + describe('blacklist', () => { + const exampleBlacklistedLog = { + address: '0xdfc7127593c8Af1a17146893F10e08528F4C2AA7', + blockNumber: 21892325, + transactionHash: '0xecb840d31f006d39b5fd5ff3cee63b8dcf9cbe4c6c42b38ad110eb4c61de865a', + transactionIndex: 102, + blockHash: '0xdca05d2e51690a481001ec0b2494c973a5293eb4ae480af8fb4103af0a080e0c', + logIndex: 287, + removed: false, + id: 'log_5db89b85', + returnValues: { + '0': '0xa233108b33dc77F1Eee9d183eE1DC9725E76d475', + '1': '0xc92EBeCDa030234C10e149bEEAD6bba61197531a', + '2': '99900000000000000', + '3': 'bRBTC', + '4': '0x00', + '5': '18', + '6': '1', + _tokenAddress: '0xa233108b33dc77F1Eee9d183eE1DC9725E76d475', + _to: '0xc92EBeCDa030234C10e149bEEAD6bba61197531a', + _amount: '99900000000000000', + _symbol: 'bRBTC', + _userData: '0x00', + _decimals: '18', + _granularity: '1' + }, + event: 'Cross', + signature: '0x33409cca56f705a7bbed38b7db57cf3a63317f3c1b9a747bbfb3d3ecffa84f6f', + raw: { + data: '0x0000000000000000000000000000000000000000000000000162ea854d0fc00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000005625242544300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000', + topics: [ + '0x33409cca56f705a7bbed38b7db57cf3a63317f3c1b9a747bbfb3d3ecffa84f6f', + '0x000000000000000000000000a233108b33dc77f1eee9d183ee1dc9725e76d475', + '0x000000000000000000000000c92ebecda030234c10e149beead6bba61197531a' + ] + } + }; + const exampleBlacklistedTx = { + "blockHash": "0xdca05d2e51690a481001ec0b2494c973a5293eb4ae480af8fb4103af0a080e0c", + "blockNumber": 21892325, + "from": "0xc92EBeCDa030234C10e149bEEAD6bba61197531a", + "gas": 255550, + "gasPrice": "5000000000", + "hash": "0xecb840d31f006d39b5fd5ff3cee63b8dcf9cbe4c6c42b38ad110eb4c61de865a", + "input": "0x8d8773ae00000000000000000000000068e75416a99f61a8ef3186b3bee41dbf2a3fd4e8000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000c92ebecda030234c10e149beead6bba61197531a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", + "nonce": 98, + "to": "0x1dA3D286a3aBeaDb2b7677c99730D725aF58e39D", + "transactionIndex": 102, + "value": "0", + "type": 0, + "v": "0x94", + "r": "0x34b8c865bc317e94911f52331cea759e3e8e04d8b1ac5bde83c72cb1d06bf5ba", + "s": "0x65ce4779aca0f6f78ad6e5bf7659b93270c4ddc990f881bc6faf7140940f95b1" + } + const blacklistedAddress = exampleBlacklistedLog.returnValues._to; + const nonBlacklistedAddress = '0x1234567890123456789012345678901234567890' + const currentBlock = 30000000; + const chainId = 1; + + let federator; + beforeEach(() => { + federator = new Federator(MAIN_FEDERATOR, testConfig, logger, {}, web3Mock); + }) + + function copyBlacklistedLogAndTx() { + return { + log: JSON.parse(JSON.stringify(exampleBlacklistedLog)), + tx: JSON.parse(JSON.stringify(exampleBlacklistedTx)) + } + } + + function mockMethods(log, tx) { + federator.mainBridgeContract.getPastEvents = (event, { fromBlock, toBlock}) => { + if(event !== 'Cross') { + throw new Error('Invalid event'); + } + if(fromBlock !== log.blockNumber || toBlock !== log.blockNumber) { + throw new Error('Invalid block range'); + } + return [log]; + } + mockFederatorMethods(federator, { + async getTransaction(txid) { + if (txid !== log.transactionHash) { + throw new Error("unknown tx") + } + return tx; + }, + async getBlockNumber() { + return currentBlock + }, + net: { + getId: () => Promise.resolve(chainId), + } + }); + } + + it('_isBlacklistedLog/_isBlacklistedAddress/_alreadyProcessed', async () => { + const { log, tx } = copyBlacklistedLogAndTx(); + mockMethods(log, tx); + + expect(federator._isBlackListedAddress(blacklistedAddress)).toBeTruthy(); + expect(federator._isBlackListedAddress(nonBlacklistedAddress)).toBeFalsy(); + + expect(await federator._isBlackListedLog(log)).toBeTruthy(); + expect(await federator._isAlreadyProcessed(log)).toBeTruthy(); + + log.returnValues._to = nonBlacklistedAddress; + expect(await federator._isBlackListedLog(log)).toBeTruthy(); + expect(await federator._isAlreadyProcessed(log)).toBeTruthy(); + + tx.from = nonBlacklistedAddress; + expect(await federator._isBlackListedLog(log)).toBeFalsy(); + expect(await federator._isAlreadyProcessed(log)).toBeFalsy(); + + log.returnValues._userData = '0x000000000000000000000000c92ebecda030234c10e149beead6bba61197531a' + expect(await federator._isBlackListedLog(log)).toBeTruthy(); + expect(await federator._isAlreadyProcessed(log)).toBeTruthy(); + + log.returnValues._userData = "0x000000000000000000000000c92ebecda030234c10e149beead6bba61197531a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + expect(await federator._isBlackListedLog(log)).toBeTruthy(); + expect(await federator._isAlreadyProcessed(log)).toBeTruthy(); + + log.returnValues._to = blacklistedAddress; + log.returnValues._userData = '0x00'; + tx.from = nonBlacklistedAddress; + expect(await federator._isBlackListedLog(log)).toBeTruthy(); + expect(await federator._isAlreadyProcessed(log)).toBeTruthy(); + + log.returnValues._to = nonBlacklistedAddress; + expect(await federator._isBlackListedLog(log)).toBeFalsy(); + expect(await federator._isAlreadyProcessed(log)).toBeFalsy(); + }); + + it('signTransaction', async () => { + const { log, tx } = copyBlacklistedLogAndTx(); + mockMethods(log, tx); + const expectedMsg = `Refusing to sign blacklisted log ${log.id}, tx ${log.transactionHash}` + const signData = { + blockNumber: log.blockNumber, + id: log.id, + } + let signed = false; + try { + await federator.signTransaction(signData) + signed = true; + } catch (e) { + expect(e.message).toEqual(expectedMsg) + } + expect(signed).toBeFalsy(); + + log.returnValues._to = nonBlacklistedAddress; + tx.from = nonBlacklistedAddress; + const ret = await federator.signTransaction(signData) + expect(ret).toBeTruthy(); + }); + + it('_processLogs', async () => { + const { log, tx } = copyBlacklistedLogAndTx(); + mockMethods(log, tx); + const signatures = ['signature1', 'signature2'] + federator._requestSignatureFromFederators = () => signatures; + const processLogSpy = jest.spyOn(federator, '_processLog'); + const executeTransactionSpy = jest.spyOn(federator, '_executeTransaction'); + + const ctr = new ConfirmationTableReader(1, federator.confirmationTable); + await federator._processLogs(ctr, [log]); + expect(processLogSpy).toHaveBeenCalledTimes(0); + + log.returnValues._to = nonBlacklistedAddress; + tx.from = nonBlacklistedAddress; + await federator._processLogs(ctr, [log]); + expect(processLogSpy).toHaveBeenCalledTimes(1); + expect(processLogSpy).toHaveBeenCalledWith(log, signatures); + expect(executeTransactionSpy).toHaveBeenCalledTimes(1); + }); + }) }); diff --git a/sovryn-token-bridge/federator/test/web3Mock/eth.js b/sovryn-token-bridge/federator/test/web3Mock/eth.js index de82e1ee..7b80c81c 100644 --- a/sovryn-token-bridge/federator/test/web3Mock/eth.js +++ b/sovryn-token-bridge/federator/test/web3Mock/eth.js @@ -10,6 +10,7 @@ eth.getTransactionCount = () => defaults.data.ethTransactionCount; eth.getGasPrice = () => defaults.data.gasPrice; eth.estimateGas = () => defaults.data.estimatedGas; eth.getNonce = () => defaults.data.ethTransactionCount; +eth.getTransaction = () => defaults.data.receipt; // not the same return type but close let promiseSend = function () { var promiEvent = Web3PromiEvent();