Removed epoch number in transfer action. Added a mechanism for requeueing failed transfer messages.

This commit is contained in:
Kirill Fedoseev 2020-01-08 17:10:25 +07:00
parent f3077bd4c0
commit 2dec546b03
19 changed files with 232 additions and 176 deletions

View File

@ -4,4 +4,5 @@ demo/validator*/development
demo/validator*/staging
src/contracts/*/build
src/contracts/*/coverage
src/contracts/*/coverageEnv
src/tss/multi-party-ecdsa

View File

@ -30,22 +30,43 @@ contract BridgeMessageProcessor is BridgeTransitions {
}
event AppliedMessage(bytes message);
event RescheduleTransferMessage(bytes32 msgHash);
mapping(bytes32 => bool) public handledMessages;
function applyMessage(bytes memory message, bytes memory signatures) public {
(bytes32 msgHash, uint16 msgEpoch) = checkSignedMessage(message, signatures);
handledMessages[msgHash] = true;
Action msgAction = Action(uint8(message[0]));
uint16 msgEpoch;
bytes32 msgHash = message._hash();
if (msgAction == Action.CONFIRM_KEYGEN || msgAction == Action.CANCEL_KEYGEN) {
require(msgEpoch == nextEpoch, "Incorrect message epoch");
} else if (msgAction == Action.TRANSFER) {
require(msgEpoch <= epoch, "Incorrect message epoch");
// In case of transfer action, it is possible that a new epoch will start,
// until a correspondent transfer action transaction will be processed.
// In such case, if a list of provided signatures for old epoch is not sufficient,
// a message should be automatically reprocessed.
// Special event helps to find such stuck messages.
if (msgAction == Action.TRANSFER) {
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = address(this).call(abi.encodeWithSelector(
this.checkSignedMessage.selector,
epoch,
msgHash,
signatures
));
if (!success) {
emit RescheduleTransferMessage(msgHash);
return;
}
} else {
require(msgEpoch == epoch, "Incorrect message epoch");
msgEpoch = message._decodeEpoch();
checkSignedMessage(msgEpoch, msgHash, signatures);
if (msgAction == Action.CONFIRM_KEYGEN || msgAction == Action.CANCEL_KEYGEN) {
require(msgEpoch == nextEpoch, "Incorrect message epoch");
} else {
require(msgEpoch == epoch, "Incorrect message epoch");
}
}
handledMessages[msgHash] = true;
if (msgAction == Action.CONFIRM_KEYGEN) {
// [3,22] - foreign address bytes
@ -90,8 +111,8 @@ contract BridgeMessageProcessor is BridgeTransitions {
require(message.length == 32, "Incorrect message length");
_cancelKeygen();
} else if (msgAction == Action.TRANSFER) {
// [3,34] - txHash, [35,54] - address, [55,66] - value
require(message.length == 67, "Incorrect message length");
// [1,32] - txHash, [33,52] - address, [53,64] - value
require(message.length == 65, "Incorrect message length");
(address to, uint96 value) = message._decodeTransfer();
_transfer(to, value);
} else if (msgAction == Action.CHANGE_MIN_PER_TX_LIMIT) {
@ -114,30 +135,25 @@ contract BridgeMessageProcessor is BridgeTransitions {
require(message.length == 32, "Incorrect message length");
uint96 limit = message._decodeUint96();
_decreaseExecutionMinLimit(limit);
} else { // Action.CHANGE_RANGE_SIZE
} else {// Action.CHANGE_RANGE_SIZE
// [3,4] - rangeSize, [5,31] - extra data
require(message.length == 32, "Incorrect message length");
uint16 rangeSize = message._decodeUint16();
_changeRangeSize(rangeSize);
} // invalid actions will not reach this line, since casting uint8 to Action will revert execution
}
// invalid actions will not reach this line, since casting uint8 to Action will revert execution
emit AppliedMessage(message);
}
function checkSignedMessage(bytes memory message, bytes memory signatures) public view returns (bytes32, uint16) {
function checkSignedMessage(uint16 msgEpoch, bytes32 msgHash, bytes memory signatures) public view {
require(signatures.length % SIGNATURE_SIZE == 0, "Incorrect signatures length");
bytes32 msgHash = message._hash();
require(!handledMessages[msgHash], "Tx was already handled");
uint16 msgEpoch;
assembly {
msgEpoch := mload(add(message, 3))
}
require(msgEpoch > 0 && msgEpoch <= nextEpoch, "Invalid epoch number");
require(msgEpoch > 0 && (msgEpoch == epoch || msgEpoch == nextEpoch), "Invalid epoch number");
uint signaturesNum = signatures.length / SIGNATURE_SIZE;
require(signaturesNum >= getThreshold(msgEpoch), "Not enough signatures");
address[] memory possibleValidators = getValidators(msgEpoch);
@ -145,6 +161,7 @@ contract BridgeMessageProcessor is BridgeTransitions {
bytes32 s;
uint8 v;
uint16 validSignatures = 0;
for (uint i = 0; i < signaturesNum; i++) {
uint offset = i * SIGNATURE_SIZE;
@ -155,15 +172,14 @@ contract BridgeMessageProcessor is BridgeTransitions {
}
address signer = ecrecover(msgHash, v, r, s);
uint j;
for (j = 0; j < possibleValidators.length; j++) {
for (uint j = 0; j < possibleValidators.length; j++) {
if (possibleValidators[j] == signer) {
delete possibleValidators[j];
validSignatures++;
break;
}
}
require(j != possibleValidators.length, "Not a validator signature");
}
return (msgHash, msgEpoch);
require(validSignatures >= getThreshold(msgEpoch), "Not enough valid signatures");
}
}

View File

@ -45,6 +45,8 @@ contract BridgeTransitions is BridgeEpochs, BridgeStates, BridgeConfig {
nextEpoch++;
_initNextEpoch(getValidators(), getThreshold(), getCloseEpoch());
_forceSign();
if (getCloseEpoch()) {
state = State.CLOSING_EPOCH;
emit EpochClose(epoch);
@ -52,8 +54,6 @@ contract BridgeTransitions is BridgeEpochs, BridgeStates, BridgeConfig {
state = State.VOTING;
emit EpochEnd(epoch);
}
_forceSign();
}
function _addValidator(address validator) internal voting {

View File

@ -4,6 +4,11 @@ library MessageDecode {
// [0] - action type
// [1,2] - epoch
// [3..] - payload
function _decodeEpoch(bytes memory message) internal pure returns (uint16 a) {
assembly {
a := mload(add(message, 3))
}
}
function _decodeUint16(bytes memory message) internal pure returns (uint16 a) {
assembly {
a := mload(add(message, 5))
@ -30,8 +35,8 @@ library MessageDecode {
function _decodeTransfer(bytes memory message) internal pure returns (address a, uint96 b) {
assembly {
a := mload(add(message, 55))
b := mload(add(message, 67))
a := mload(add(message, 53))
b := mload(add(message, 65))
}
}
}

View File

@ -11,8 +11,8 @@ library MessageHash {
if (message.length == 32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message));
}
if (message.length == 67) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n67", message));
if (message.length == 65) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n65", message));
}
revert("Incorrect message length");
}

View File

@ -1,7 +1,8 @@
const { expect } = require('chai')
const {
State, Action, getDeployResult, expectEventInLogs, buildMessage, sign, stripHex, skipBlocks
State, Action, getDeployResult, expectEventInLogs, buildMessage, sign, stripHex, skipBlocks,
keccak256
} = require('./utils')
const EthToBncBridge = artifacts.require('EthToBncBridge')
@ -125,80 +126,54 @@ contract('EthToBncBridge', async (accounts) => {
})
describe('signatures checks', async () => {
const keygenMessage = buildMessage(Action.CONFIRM_KEYGEN, 1, '0000000000000000000000000000000000000000')
const votingMessage0 = buildMessage(Action.START_VOTING, 0)
const votingMessage1 = buildMessage(Action.START_VOTING, 1)
const validatorMessage = buildMessage(Action.ADD_VALIDATOR, 1, stripHex(accounts[0]), '000000000000000000')
const transferMessage = buildMessage(Action.TRANSFER, 1, '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
const messages = [keygenMessage, votingMessage1, validatorMessage, transferMessage]
const invalidMessage = `${votingMessage1}00`
const message = '0x010203'
const messageHash = keccak256(message)
let signature1
let signature2
let signature3
before(async () => {
bridge = await deployBridge()
signature1 = stripHex(await sign(validators[0], message))
signature2 = stripHex(await sign(validators[1], message))
signature3 = stripHex(await sign(validators[2], message))
})
for (let i = 0; i < messages.length; i += 1) {
const message = messages[i]
it(`should accept 2 or 3 correct signatures for message of length ${(message.length - 2) / 2} bytes`, async () => {
const signature1 = stripHex(await sign(validators[0], message))
const signature2 = stripHex(await sign(validators[1], message))
const signature3 = stripHex(await sign(validators[2], message))
await bridge.checkSignedMessage(message, `0x${signature1}${signature2}`).should.be.fulfilled
await bridge.checkSignedMessage(message, `0x${signature2}${signature1}`).should.be.fulfilled
await bridge.checkSignedMessage(message, `0x${signature2}${signature3}`).should.be.fulfilled
await bridge.checkSignedMessage(message, `0x${signature3}${signature1}`).should.be.fulfilled
await bridge.checkSignedMessage(message, `0x${signature1}${signature2}${signature3}`).should.be.fulfilled
await bridge.checkSignedMessage(message, `0x${signature3}${signature1}${signature2}`).should.be.fulfilled
})
}
it(`should not accept correct signatures for message of length ${(invalidMessage.length - 2) / 2} bytes`, async () => {
const signature1 = stripHex(await sign(validators[0], invalidMessage))
const signature2 = stripHex(await sign(validators[1], invalidMessage))
await bridge.checkSignedMessage(invalidMessage, `0x${signature1}${signature2}`).should.be.rejected
it('should accept 2 or 3 correct signatures', async () => {
await bridge.checkSignedMessage(1, messageHash, `0x${signature1}${signature2}`).should.be.fulfilled
await bridge.checkSignedMessage(1, messageHash, `0x${signature2}${signature1}`).should.be.fulfilled
await bridge.checkSignedMessage(1, messageHash, `0x${signature2}${signature3}`).should.be.fulfilled
await bridge.checkSignedMessage(1, messageHash, `0x${signature3}${signature1}`).should.be.fulfilled
await bridge.checkSignedMessage(1, messageHash, `0x${signature1}${signature2}${signature3}`).should.be.fulfilled
await bridge.checkSignedMessage(1, messageHash, `0x${signature3}${signature1}${signature2}`).should.be.fulfilled
})
it('should not accept empty signatures', async () => {
await bridge.checkSignedMessage(keygenMessage, '0x').should.be.rejected
await bridge.checkSignedMessage(1, messageHash, '0x').should.be.rejected
})
it('should not accept signatures of wrong length', async () => {
const signature1 = stripHex(await sign(validators[0], keygenMessage))
const signature2 = stripHex(await sign(validators[1], keygenMessage))
await bridge.checkSignedMessage(keygenMessage, `0x${signature1}${signature2}00`).should.be.rejected
await bridge.checkSignedMessage(1, messageHash, `0x${signature1}${signature2}00`).should.be.rejected
})
it('should not accept 1 correct signature', async () => {
const signature1 = stripHex(await sign(validators[0], keygenMessage))
await bridge.checkSignedMessage(keygenMessage, `0x${signature1}`).should.be.rejected
await bridge.checkSignedMessage(1, messageHash, `0x${signature1}`).should.be.rejected
})
it('should not accept message with 0 epoch', async () => {
const signature1 = stripHex(await sign(validators[0], votingMessage0))
const signature2 = stripHex(await sign(validators[1], votingMessage0))
await bridge.checkSignedMessage(votingMessage0, `0x${signature1}${signature2}`).should.be.rejected
await bridge.checkSignedMessage(0, messageHash, `0x${signature1}${signature2}`).should.be.rejected
})
it('should not accept repeated correct signatures', async () => {
const signature1 = stripHex(await sign(validators[0], keygenMessage))
const signature2 = stripHex(await sign(validators[1], keygenMessage))
await bridge.checkSignedMessage(keygenMessage, `0x${signature1}${signature1}`).should.be.rejected
await bridge.checkSignedMessage(keygenMessage, `0x${signature1}${signature2}${signature1}`).should.be.rejected
it('should not accept repeated correct signatures as valid', async () => {
await bridge.checkSignedMessage(1, messageHash, `0x${signature1}${signature1}`).should.be.rejected
await bridge.checkSignedMessage(1, messageHash, `0x${signature1}${signature2}${signature1}`).should.be.fulfilled
})
it('should accept signatures only from validators', async () => {
const signature1 = stripHex(await sign(validators[0], keygenMessage))
const signature2 = stripHex(await sign(validators[1], keygenMessage))
const wrongSignature = stripHex(await sign(accounts[5], keygenMessage))
it('should accept signatures if enough valid signatures', async () => {
const wrongSignature = stripHex(await sign(accounts[5], message))
await bridge.checkSignedMessage(keygenMessage, `0x${signature1}${wrongSignature}`).should.be.rejected
await bridge.checkSignedMessage(keygenMessage, `0x${signature1}${signature2}${wrongSignature}`).should.be.rejected
await bridge.checkSignedMessage(1, messageHash, `0x${signature1}${wrongSignature}`).should.be.rejected
await bridge.checkSignedMessage(1, messageHash, `0x${signature1}${signature2}${wrongSignature}`).should.be.fulfilled
})
})
@ -241,39 +216,34 @@ contract('EthToBncBridge', async (accounts) => {
const startVotingMessage2 = buildMessage(Action.START_VOTING, 2)
const transferMessage15min = buildMessage(
Action.TRANSFER,
1,
'1111111111111111111111111111111111111111111111111111111111111111',
stripHex(accounts[5]),
'0000000000000002540be401'
)
const transferMessage15min1s = buildMessage(
Action.TRANSFER,
'1111111111111111111111111111111111111111111111111111111111111111',
stripHex(accounts[5]),
'0000000000000002540be402'
)
const transferMessage15max = buildMessage(
Action.TRANSFER,
1,
'2222222222222222222222222222222222222222222222222222222222222222',
stripHex(accounts[5]),
'00000000000000e8d4a51001'
)
const transferMessage15low = buildMessage(
Action.TRANSFER,
1,
'1111111111111111111111111111111111111111111111111111111111111111',
stripHex(accounts[5]),
'000000000000000000000064'
)
const transferMessage15high = buildMessage(
Action.TRANSFER,
1,
'1111111111111111111111111111111111111111111111111111111111111111',
stripHex(accounts[5]),
'00000000000000e8d4a51002'
)
const transferMessage25min = buildMessage(
Action.TRANSFER,
2,
'3333333333333333333333333333333333333333333333333333333333333333',
stripHex(accounts[5]),
'0000000000000002540be401'
)
const transferMessage1err = buildMessage(Action.TRANSFER, 1)
const changeMinPerTxLimit1 = buildMessage(Action.CHANGE_MIN_PER_TX_LIMIT, 1, '0000000000000002540be401', '0000000000000000000000000000000000')
const changeMinPerTxLimit1high = buildMessage(Action.CHANGE_MIN_PER_TX_LIMIT, 1, '100000000000000000000000', '0000000000000000000000000000000000')
@ -298,6 +268,7 @@ contract('EthToBncBridge', async (accounts) => {
Action.UNKNOWN_MESSAGE,
1
)
const invalidMessage = buildMessage(Action.CONFIRM_KEYGEN, 1, '00')
const validMessages = [
confirmKeygenMessage11, confirmKeygenMessage12, confirmKeygenMessage1err, startVotingMessage1,
startVotingMessage1err, confirmCloseEpochMessage1, confirmCloseEpochMessage1err,
@ -311,14 +282,13 @@ contract('EthToBncBridge', async (accounts) => {
confirmFundsTransferMessage1, confirmFundsTransferMessage1err, cancelKeygenMessage1,
cancelKeygenMessage21, cancelKeygenMessage22, cancelKeygenMessage2err, confirmKeygenMessage2,
startVotingMessage2, transferMessage15min, transferMessage15max, transferMessage15low,
transferMessage15high, transferMessage25min, transferMessage1err, changeMinPerTxLimit1,
changeMinPerTxLimit1high, changeMinPerTxLimit1low, changeMinPerTxLimit1err,
changeMaxPerTxLimit1, changeMaxPerTxLimit1low, changeMaxPerTxLimit1err,
decreaseExecutionMinLimit1low, decreaseExecutionMinLimit11, decreaseExecutionMinLimit12,
decreaseExecutionMinLimit1err, increaseExecutionMaxLimit1low, increaseExecutionMaxLimit11,
increaseExecutionMaxLimit12, increaseExecutionMaxLimit1err, changeRangeSizeMessage10,
changeRangeSizeMessage11, changeRangeSizeMessage1max, changeRangeSizeMessage1err,
unknownMessage
transferMessage15high, transferMessage1err, changeMinPerTxLimit1, changeMinPerTxLimit1high,
changeMinPerTxLimit1low, changeMinPerTxLimit1err, changeMaxPerTxLimit1,
changeMaxPerTxLimit1low, changeMaxPerTxLimit1err, decreaseExecutionMinLimit1low,
decreaseExecutionMinLimit11, decreaseExecutionMinLimit12, decreaseExecutionMinLimit1err,
increaseExecutionMaxLimit1low, increaseExecutionMaxLimit11, increaseExecutionMaxLimit12,
increaseExecutionMaxLimit1err, changeRangeSizeMessage10, changeRangeSizeMessage11,
changeRangeSizeMessage1max, changeRangeSizeMessage1err, unknownMessage, invalidMessage
]
const validSignatures = {}
@ -328,6 +298,7 @@ contract('EthToBncBridge', async (accounts) => {
}
before(async () => {
validSignatures[transferMessage15min1s] = await sign(validators[0], transferMessage15min1s)
for (let i = 0; i < validMessages.length; i += 1) {
const message = validMessages[i]
@ -370,7 +341,11 @@ contract('EthToBncBridge', async (accounts) => {
const signature2 = stripHex(await sign(validators[1], confirmKeygenMessage11))
await applyMessage(confirmKeygenMessage11)
await bridge.checkSignedMessage(confirmKeygenMessage11, `0x${signature1}${signature2}`).should.be.rejected
await bridge.checkSignedMessage(1, keccak256(confirmKeygenMessage11), `0x${signature1}${signature2}`).should.be.rejected
})
it('should not accept invalid length message', async () => {
await applyMessage(invalidMessage).should.be.rejected
})
it('should not be able to apply keygen confirm message for 2nd epoch', async () => {
@ -948,23 +923,16 @@ contract('EthToBncBridge', async (accounts) => {
await applyMessage(transferMessage15high).should.be.rejected
})
it('should not accept transfer message for next epoch', async () => {
await applyMessage(startVotingMessage1)
await token.transfer(bridge.address, EXECUTION_MIN_LIMIT, { from: accounts[1] })
await applyMessage(transferMessage25min).should.be.rejected
})
it('should accept transfer message for previous epoch', async () => {
await applyMessage(startVotingMessage1)
await applyMessage(startKeygenMessage11)
await applyMessage(confirmKeygenMessage2)
await applyMessage(confirmFundsTransferMessage1)
await applyMessage(transferMessage15min).should.be.fulfilled
it('should request transfer message reschedule', async () => {
const { logs } = await applyMessage(transferMessage15min1s).should.be.fulfilled
expectEventInLogs(logs, 'RescheduleTransferMessage', {
msgHash: keccak256(transferMessage15min1s)
})
expect(await token.balanceOf(bridge.address)).to.bignumber.equal('0')
expect(await token.balanceOf(accounts[5])).to.bignumber.equal('0')
expect(
await token.allowance(bridge.address, accounts[5])
).to.bignumber.equal(EXECUTION_MIN_LIMIT)
).to.bignumber.equal('0')
})
it('should not accept message with wrong length', async () => {

View File

@ -94,8 +94,12 @@ function padZeros(arg, len) {
return s
}
function buildMessage(type, epoch, ...args) {
return `0x${padZeros(type, 2)}${padZeros(epoch, 4)}${args.reduce(((previousValue, currentValue) => previousValue + currentValue), '')}`
function buildMessage(type, ...args) {
const epoch = args[0]
if (typeof epoch === 'number') {
return `0x${padZeros(type, 2)}${padZeros(epoch, 4)}${args.slice(1).reduce(((previousValue, currentValue) => previousValue + currentValue), '')}`
}
return `0x${padZeros(type, 2)}${args.reduce(((previousValue, currentValue) => previousValue + currentValue), '')}`
}
async function sign(address, data) {
@ -119,6 +123,10 @@ async function skipBlocks(n = 1) {
}
}
function keccak256(message) {
return web3.eth.accounts.hashMessage(message)
}
module.exports = {
State,
Action,
@ -127,5 +135,6 @@ module.exports = {
buildMessage,
stripHex,
sign,
skipBlocks
skipBlocks,
keccak256
}

View File

@ -11,8 +11,8 @@ library MessageHash {
if (message.length == 32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message));
}
if (message.length == 67) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n67", message));
if (message.length == 65) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n65", message));
}
revert("Incorrect message length");
}

View File

@ -82,7 +82,7 @@ contract('SharedDB', async (accounts) => {
'0x000102',
'0x000102030405060708090a0b0c0d0e0f10111213141516',
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f',
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142'
'0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40'
]
describe('add signature', async () => {

View File

@ -9,6 +9,6 @@ COPY ./bncWatcher/package.json ./
RUN npm install
COPY ./bncWatcher/index.js ./src/
COPY ./shared/db.js ./shared/logger.js ./shared/crypto.js ./shared/amqp.js ./shared/wait.js ./shared/binanceClient.js ./shared/
COPY ./shared/db.js ./shared/logger.js ./shared/crypto.js ./shared/amqp.js ./shared/wait.js ./shared/binanceClient.js ./shared/ethProvider.js ./shared/
ENTRYPOINT ["node", "src/index.js"]

View File

@ -1,17 +1,17 @@
const axios = require('axios')
const BN = require('bignumber.js')
const fs = require('fs')
const { computeAddress } = require('ethers').utils
const ethers = require('ethers')
const logger = require('../shared/logger')
const redis = require('../shared/db')
const { publicKeyToAddress } = require('../shared/crypto')
const createProvider = require('../shared/ethProvider')
const { hexAddressToBncAddress } = require('../shared/crypto')
const { delay } = require('../shared/wait')
const { connectRabbit, assertQueue } = require('../shared/amqp')
const { getTx, getBlockTime, fetchNewTransactions } = require('../shared/binanceClient')
const {
PROXY_URL, RABBITMQ_URL
PROXY_URL, RABBITMQ_URL, HOME_RPC_URL, HOME_BRIDGE_ADDRESS
} = process.env
const FOREIGN_FETCH_INTERVAL = parseInt(process.env.FOREIGN_FETCH_INTERVAL, 10)
@ -20,17 +20,19 @@ const FOREIGN_FETCH_MAX_TIME_INTERVAL = parseInt(process.env.FOREIGN_FETCH_MAX_T
const proxyHttpClient = axios.create({ baseURL: PROXY_URL })
const bridgeAbi = [
'function getForeignAddress(uint16 epoch) view returns (bytes20)'
]
const homeProvider = createProvider(HOME_RPC_URL)
const bridge = new ethers.Contract(HOME_BRIDGE_ADDRESS, bridgeAbi, homeProvider)
let channel
let epochTimeIntervalsQueue
function getForeignAddress(epoch) {
const keysFile = `/keys/keys${epoch}.store`
try {
const publicKey = JSON.parse(fs.readFileSync(keysFile))[5]
return publicKeyToAddress(publicKey)
} catch (e) {
return null
}
async function getForeignAddress(epoch) {
const foreignHexAddress = await bridge.getForeignAddress(epoch)
return hexAddressToBncAddress(foreignHexAddress)
}
async function fetchTimeIntervalsQueue() {
@ -99,15 +101,7 @@ async function loop() {
return
}
const address = getForeignAddress(epoch)
if (!address) {
logger.debug('Validator is not included in current epoch')
await redis.set(`foreignTime${epoch}`, endTime)
await delay(FOREIGN_FETCH_INTERVAL)
return
}
const address = await getForeignAddress(epoch)
const transactions = await fetchNewTransactions(address, startTime, endTime)
if (transactions.length === 0) {
@ -125,11 +119,9 @@ async function loop() {
if (tx.memo === '') {
const publicKeyEncoded = (await getTx(tx.txHash)).signatures[0].pub_key.value
await proxyHttpClient.post('/transfer', {
to: computeAddress(Buffer.from(publicKeyEncoded, 'base64')),
value: new BN(tx.value).multipliedBy('1e18')
.toString(16),
hash: tx.txHash,
epoch
to: ethers.utils.computeAddress(Buffer.from(publicKeyEncoded, 'base64')),
value: new BN(tx.value).multipliedBy('1e18').toString(16),
hash: tx.txHash
})
}
}

View File

@ -111,15 +111,16 @@ services:
<<: *common-environment
PROXY_URL: http://local_proxy:8001
REDIS_HOST: redis
HOME_RPC_URL:
HOME_BRIDGE_ADDRESS:
FOREIGN_URL:
FOREIGN_ASSET:
FOREIGN_FETCH_MAX_TIME_INTERVAL:
FOREIGN_FETCH_INTERVAL:
FOREIGN_FETCH_BLOCK_TIME_OFFSET:
volumes:
- '${PWD}/${TARGET_NETWORK}/keys:/keys'
networks:
- test_network
- ethereum_home_rpc_net
- binance_net
side-watcher:
build:

View File

@ -104,13 +104,13 @@ services:
<<: *common-environment
PROXY_URL: http://proxy:8001
REDIS_HOST: redis
HOME_RPC_URL:
HOME_BRIDGE_ADDRESS:
FOREIGN_URL:
FOREIGN_ASSET:
FOREIGN_FETCH_MAX_TIME_INTERVAL:
FOREIGN_FETCH_INTERVAL:
FOREIGN_FETCH_BLOCK_TIME_OFFSET:
volumes:
- '${PWD}/${TARGET_NETWORK}/keys:/keys'
networks:
- oracle_network
side-watcher:

View File

@ -26,6 +26,7 @@ const bridgeAbi = [
'event EpochClose(uint16 indexed epoch)',
'event ForceSign()',
'event RangeSizeChanged(uint16 rangeSize)',
'event RescheduleTransferMessage(bytes32 msgHash)',
'function getForeignAddress(uint16 epoch) view returns (bytes20)',
'function getThreshold(uint16 epoch) view returns (uint16)',
'function getParties(uint16 epoch) view returns (uint16)',
@ -41,6 +42,7 @@ let signQueue
let keygenQueue
let cancelKeygenQueue
let epochTimeIntervalsQueue
let rescheduleTransferQueue
let chainId
let blockNumber
let epoch
@ -185,6 +187,17 @@ async function sendEpochClose() {
redisTx.incr(`foreignNonce${epoch}`)
}
async function rescheduleTransferMessage(transactionHash, msgHash) {
const tx = await provider.getTransaction(transactionHash)
if (tx.from === validatorAddress) {
logger.debug(`Rescheduling transfer action for message ${msgHash}`)
rescheduleTransferQueue.send({
msgHash,
blockNumber: tx.blockNumber
})
}
}
async function initialize() {
channel = await connectRabbit(RABBITMQ_URL)
exchangeQueue = await assertQueue(channel, 'exchangeQueue')
@ -192,6 +205,7 @@ async function initialize() {
keygenQueue = await assertQueue(channel, 'keygenQueue')
cancelKeygenQueue = await assertQueue(channel, 'cancelKeygenQueue')
epochTimeIntervalsQueue = await assertQueue(channel, 'epochTimeIntervalsQueue')
rescheduleTransferQueue = await assertQueue(channel, 'rescheduleTransferQueue')
activeEpoch = !!(await redis.get('activeEpoch'))
@ -244,6 +258,7 @@ async function initialize() {
await resetFutureMessages(channel, exchangeQueue, blockNumber)
await resetFutureMessages(channel, signQueue, blockNumber)
await resetFutureMessages(channel, epochTimeIntervalsQueue, blockNumber)
await resetFutureMessages(channel, rescheduleTransferQueue, blockNumber)
logger.debug('Sending start commands')
await axios.get(`${KEYGEN_CLIENT_URL}/start`)
await axios.get(`${SIGN_CLIENT_URL}/start`)
@ -339,6 +354,9 @@ async function loop() {
rangeSizeStartBlock = curBlockNumber
logger.debug(`Range size updated to ${rangeSize} at block ${rangeSizeStartBlock}`)
break
case 'RescheduleTransferMessage':
await rescheduleTransferMessage(bridgeEvents[i].transactionHash, event.values.msgHash)
break
default:
logger.warn('Unknown event %o', event)
}

View File

@ -23,6 +23,7 @@ const bridgeAbi = [
'function getNextCloseEpoch() view returns (bool)',
'function state() view returns (uint8)',
'function getNextPartyId(address partyAddress) view returns (uint16)',
'function handledMessages(bytes32 msgHash) view returns (bool)',
'function applyMessage(bytes message, bytes signatures)'
]
const sharedDbAbi = [

View File

@ -335,11 +335,16 @@ async function voteIncreaseExecutionMaxLimit(req, res) {
async function transfer(req, res) {
logger.info('Transfer start')
const {
hash, to, value, epoch
hash, to, value
} = req.body
if (ethers.utils.isHexString(to, 20)) {
logger.info(`Calling transfer to ${to}, 0x${value} tokens`)
await processMessage(Action.TRANSFER, epoch, hash, to, padZeros(value, 24))
const message = `${padZeros(Action.TRANSFER.toString(16), 2)}${hash}${to.slice(2)}${padZeros(value, 24)}`
if (await bridge.handledMessages(ethers.utils.hashMessage(message))) {
logger.info('This tx hash was already processed, skipping')
} else {
await processMessage(Action.TRANSFER, hash, to, padZeros(value, 24))
}
}
res.send()
logger.info('Transfer end')

View File

@ -16,7 +16,9 @@ const SIDE_MAX_FETCH_RANGE_SIZE = parseInt(process.env.SIDE_MAX_FETCH_RANGE_SIZE
const bridgeAbi = [
'function applyMessage(bytes message, bytes signatures)',
'function getThreshold(uint16 epoch) view returns (uint16)',
'function getValidators(uint16 epoch) view returns (address[])'
'function getValidators(uint16 epoch) view returns (address[])',
'function handledMessages(bytes32 msgHash) view returns (bool)',
'function epoch() view returns (uint16)'
]
const sharedDbAbi = [
'event NewSignature(address indexed signer, bytes32 msgHash)',
@ -24,6 +26,7 @@ const sharedDbAbi = [
'function getSignatures(bytes32 msgHash, address[] validators) view returns (bytes)',
'function isResponsibleToSend(bytes32 msgHash, address[] validators, uint16 threshold, address validatorAddress) view returns (bool)'
]
const ACTION_TRANSFER = 10
const sideProvider = createProvider(SIDE_RPC_URL)
const homeProvider = createProvider(HOME_RPC_URL)
@ -35,13 +38,16 @@ const validatorAddress = ethers.utils.computeAddress(`0x${VALIDATOR_PRIVATE_KEY}
let blockNumber
let homeSendQueue
let rescheduleTransferQueue
let channel
let curBlockNumber
async function handleNewSignature(event) {
const { msgHash } = event.values
async function handleNewMessage(msgHash) {
const message = await sharedDb.signedMessages(msgHash)
const epoch = parseInt(message.slice(4, 8), 16)
const messageAction = parseInt(message.slice(2, 4), 16)
const epoch = messageAction === ACTION_TRANSFER
? await bridge.epoch()
: parseInt(message.slice(4, 8), 16)
const [threshold, validators] = await Promise.all([
bridge.getThreshold(epoch),
bridge.getValidators(epoch)
@ -56,20 +62,27 @@ async function handleNewSignature(event) {
if (isResponsibleToSend) {
logger.info(`This validator is responsible to send message ${message}`)
const signatures = await retry(
() => sharedDb.getSignatures(msgHash, validators),
-1,
(curSignatures) => (curSignatures.length - 2) / 130 >= threshold
)
const isAlreadyProcessed = await bridge.handledMessages(msgHash)
if (isAlreadyProcessed) {
logger.debug('This message was already processed')
} else {
const signatures = await retry(
() => sharedDb.getSignatures(msgHash, validators),
-1,
(curSignatures) => (curSignatures.length - 2) / 130 >= threshold
)
const requiredSignatures = signatures.slice(0, 2 + 130 * threshold)
const requiredSignatures = signatures.slice(0, 2 + 130 * threshold)
const data = await bridge.interface.functions.applyMessage.encode([message, requiredSignatures])
const data = await bridge.interface.functions.applyMessage.encode(
[message, requiredSignatures]
)
homeSendQueue.send({
data,
blockNumber: curBlockNumber
})
homeSendQueue.send({
data,
blockNumber: curBlockNumber
})
}
} else {
logger.debug(`This validator is not responsible to send message ${message}`)
}
@ -100,7 +113,7 @@ async function loop() {
curBlockNumber = bridgeEvents[i].blockNumber
const event = sharedDb.interface.parseLog(bridgeEvents[i])
logger.trace('Consumed event %o %o', event, bridgeEvents[i])
await handleNewSignature(event)
await handleNewMessage(event.values.msgHash)
}
blockNumber = endBlock + 1
@ -112,6 +125,13 @@ async function loop() {
async function initialize() {
channel = await connectRabbit(RABBITMQ_URL)
homeSendQueue = await assertQueue(channel, 'homeSendQueue')
rescheduleTransferQueue = await assertQueue(channel, 'rescheduleTransferQueue')
rescheduleTransferQueue.consume(async (msg) => {
const { msgHash } = JSON.parse(msg.content)
await handleNewMessage(msgHash)
channel.ack(msg)
})
blockNumber = (parseInt(await redis.get('sideBlock'), 10) + 1) || parseInt(SIDE_START_BLOCK, 10)

View File

@ -2,7 +2,8 @@
"name": "get-bridge-history",
"version": "0.0.1",
"dependencies": {
"ethers": "4.0.37"
"ethers": "4.0.37",
"bech32": "1.1.3"
},
"engines": {
"node": ">=10.6.0"

View File

@ -1,4 +1,5 @@
const ethers = require('ethers')
const bech32 = require('bech32')
const {
HOME_RPC_URL, HOME_BRIDGE_ADDRESS, WITH_SIGNATURES, SIDE_RPC_URL, SIDE_SHARED_DB_ADDRESS
@ -42,16 +43,26 @@ const actionNames = [
'ADD_VALIDATOR',
'REMOVE_VALIDATOR',
'CHANGE_THRESHOLD',
'CHANGE_RANGE_SIZE',
'CHANGE_CLOSE_EPOCH',
'START_KEYGEN',
'CANCEL_KEYGEN',
'TRANSFER'
'TRANSFER',
'CHANGE_MIN_PER_TX_LIMIT',
'CHANGE_MAX_PER_TX_LIMIT',
'INCREASE_EXECUTION_MAX_TX_LIMIT',
'DECREASE_EXECUTION_MIN_TX_LIMIT',
'CHANGE_RANGE_SIZE'
]
let bridge
let sharedDb
function hexAddressToBncAddress(hexAddress) {
const addressBytes = Buffer.from(hexAddress, 'hex')
const words = bech32.toWords(addressBytes)
return bech32.encode('tbnb', words)
}
function processEvent(event) {
const { message } = event.values
@ -67,8 +78,7 @@ function processEvent(event) {
case Action.CONFIRM_KEYGEN:
return {
...baseMsg,
x: message.slice(8, 72),
y: message.slice(72, 136)
foreignAddress: hexAddressToBncAddress(message.slice(8, 48))
}
case Action.CONFIRM_FUNDS_TRANSFER:
case Action.CONFIRM_CLOSE_EPOCH:
@ -112,6 +122,15 @@ function processEvent(event) {
to: `0x${message.slice(72, 112)}`,
value: `0x${message.slice(112, 136)}`
}
case Action.CHANGE_MIN_PER_TX_LIMIT:
case Action.CHANGE_MAX_PER_TX_LIMIT:
case Action.INCREASE_EXECUTION_MAX_TX_LIMIT:
case Action.DECREASE_EXECUTION_MIN_TX_LIMIT:
return {
...baseMsg,
limit: `0x${message.slice(8, 32)}`,
attempt: parseInt(message.slice(32, 66), 16)
}
default:
return {
...baseMsg,