|
| 1 | +import {contractId} from "../../utils/helpers" |
| 2 | +import batchTranscodeReceiptHashes from "../../utils/batchTranscodeReceipts" |
| 3 | +import MerkleTree from "../../utils/merkleTree" |
| 4 | +import {createTranscodingOptions} from "../../utils/videoProfile" |
| 5 | +import Segment from "../../utils/segment" |
| 6 | + |
| 7 | +const Controller = artifacts.require("Controller") |
| 8 | +const BondingManager = artifacts.require("BondingManager") |
| 9 | +const JobsManager = artifacts.require("JobsManager") |
| 10 | +const AdjustableRoundsManager = artifacts.require("AdjustableRoundsManager") |
| 11 | +const LivepeerToken = artifacts.require("LivepeerToken") |
| 12 | +const LivepeerTokenFaucet = artifacts.require("LivepeerTokenFaucet") |
| 13 | + |
| 14 | +contract("DoubleClaimSegmentSlashing", accounts => { |
| 15 | + let controller |
| 16 | + let bondingManager |
| 17 | + let roundsManager |
| 18 | + let jobsManager |
| 19 | + let token |
| 20 | + |
| 21 | + let transcoder |
| 22 | + let delegator1 |
| 23 | + let delegator2 |
| 24 | + let watcher |
| 25 | + let broadcaster |
| 26 | + |
| 27 | + let roundLength |
| 28 | + |
| 29 | + before(async () => { |
| 30 | + transcoder = accounts[0] |
| 31 | + delegator1 = accounts[1] |
| 32 | + delegator2 = accounts[2] |
| 33 | + watcher = accounts[3] |
| 34 | + broadcaster = accounts[3] |
| 35 | + |
| 36 | + controller = await Controller.deployed() |
| 37 | + |
| 38 | + const bondingManagerAddr = await controller.getContract(contractId("BondingManager")) |
| 39 | + bondingManager = await BondingManager.at(bondingManagerAddr) |
| 40 | + |
| 41 | + const roundsManagerAddr = await controller.getContract(contractId("RoundsManager")) |
| 42 | + roundsManager = await AdjustableRoundsManager.at(roundsManagerAddr) |
| 43 | + |
| 44 | + const jobsManagerAddr = await controller.getContract(contractId("JobsManager")) |
| 45 | + jobsManager = await JobsManager.at(jobsManagerAddr) |
| 46 | + |
| 47 | + // Set verification rate to 1 out of 1 segments, so every segment is challenged |
| 48 | + await jobsManager.setVerificationRate(1) |
| 49 | + // Set double claim segment slash amount to 20% |
| 50 | + await jobsManager.setDoubleClaimSegmentSlashAmount(200000) |
| 51 | + |
| 52 | + const tokenAddr = await controller.getContract(contractId("LivepeerToken")) |
| 53 | + token = await LivepeerToken.at(tokenAddr) |
| 54 | + |
| 55 | + const faucetAddr = await controller.getContract(contractId("LivepeerTokenFaucet")) |
| 56 | + const faucet = await LivepeerTokenFaucet.at(faucetAddr) |
| 57 | + |
| 58 | + await faucet.request({from: transcoder}) |
| 59 | + await faucet.request({from: delegator1}) |
| 60 | + await faucet.request({from: delegator2}) |
| 61 | + |
| 62 | + roundLength = await roundsManager.roundLength.call() |
| 63 | + await roundsManager.mineBlocks(roundLength.toNumber() * 1000) |
| 64 | + await roundsManager.initializeRound() |
| 65 | + |
| 66 | + await token.approve(bondingManager.address, 1000, {from: transcoder}) |
| 67 | + await bondingManager.bond(1000, transcoder, {from: transcoder}) |
| 68 | + await bondingManager.transcoder(10, 5, 1, {from: transcoder}) |
| 69 | + |
| 70 | + await token.approve(bondingManager.address, 1000, {from: delegator1}) |
| 71 | + await bondingManager.bond(1000, transcoder, {from: delegator1}) |
| 72 | + |
| 73 | + await token.approve(bondingManager.address, 1000, {from: delegator2}) |
| 74 | + await bondingManager.bond(1000, transcoder, {from: delegator2}) |
| 75 | + |
| 76 | + // Fast forward to new round with locked in active transcoder set |
| 77 | + await roundsManager.mineBlocks(roundLength.toNumber()) |
| 78 | + await roundsManager.initializeRound() |
| 79 | + }) |
| 80 | + |
| 81 | + it("watcher should slash a transcoder for double claiming segments", async () => { |
| 82 | + await jobsManager.deposit({from: broadcaster, value: 1000}) |
| 83 | + |
| 84 | + const endBlock = (await roundsManager.blockNum()).add(100) |
| 85 | + await jobsManager.job("foo", createTranscodingOptions(["foo", "bar"]), 1, endBlock, {from: broadcaster}) |
| 86 | + |
| 87 | + let rand = web3.eth.getBlock(web3.eth.blockNumber).hash |
| 88 | + await roundsManager.mineBlocks(1) |
| 89 | + await roundsManager.setBlockHash(rand) |
| 90 | + |
| 91 | + // Segment data hashes |
| 92 | + const dataHashes = [ |
| 93 | + "0x80084bf2fba02475726feb2cab2d8215eab14bc6bdd8bfb2c8151257032ecd8b", |
| 94 | + "0xb039179a8a4ce2c252aa6f2f25798251c19b75fc1508d9d511a191e0487d64a7", |
| 95 | + "0x263ab762270d3b73d3e2cddf9acc893bb6bd41110347e5d5e4bd1d3c128ea90a", |
| 96 | + "0x4ce8765e720c576f6f5a34ca380b3de5f0912e6e3cc5355542c363891e54594b" |
| 97 | + ] |
| 98 | + |
| 99 | + // Segments |
| 100 | + const segments = dataHashes.map((dataHash, idx) => new Segment("foo", idx, dataHash, broadcaster)) |
| 101 | + |
| 102 | + // Transcoded data hashes |
| 103 | + const tDataHashes = [ |
| 104 | + "0x42538602949f370aa331d2c07a1ee7ff26caac9cc676288f94b82eb2188b8465", |
| 105 | + "0xa0b37b8bfae8e71330bd8e278e4a45ca916d00475dd8b85e9352533454c9fec8", |
| 106 | + "0x9f2898da52dedaca29f05bcac0c8e43e4b9f7cb5707c14cc3f35a567232cec7c", |
| 107 | + "0x5a082c81a7e4d5833ee20bd67d2f4d736f679da33e4bebd3838217cb27bec1d3" |
| 108 | + ] |
| 109 | + |
| 110 | + // Transcode receipts |
| 111 | + const tReceiptHashes = batchTranscodeReceiptHashes(segments, tDataHashes) |
| 112 | + |
| 113 | + // Build merkle tree |
| 114 | + const merkleTree = new MerkleTree(tReceiptHashes) |
| 115 | + |
| 116 | + const tokenStartSupply = await token.totalSupply.call() |
| 117 | + |
| 118 | + // Transcoder claims segments 0 through 3 |
| 119 | + await jobsManager.claimWork(0, [0, 3], merkleTree.getHexRoot(), {from: transcoder}) |
| 120 | + // Transcoder claims segments 0 through 3 again |
| 121 | + await jobsManager.claimWork(0, [0, 3], merkleTree.getHexRoot(), {from: transcoder}) |
| 122 | + // Wait for claims to be mined |
| 123 | + await roundsManager.mineBlocks(2) |
| 124 | + |
| 125 | + // Watcher slashes transcoder for double claiming segments |
| 126 | + // Transcoder claimed segments 0 through 3 twice |
| 127 | + await jobsManager.doubleClaimSegmentSlash(0, 0, 1, 0, {from: watcher}) |
| 128 | + |
| 129 | + // Check that the transcoder is penalized |
| 130 | + const currentRound = await roundsManager.currentRound() |
| 131 | + const doubleClaimSegmentSlashAmount = await jobsManager.doubleClaimSegmentSlashAmount.call() |
| 132 | + const penalty = Math.floor((1000 * doubleClaimSegmentSlashAmount.toNumber()) / 1000000) |
| 133 | + const expTransStakeRemaining = 1000 - penalty |
| 134 | + const expDelegatedStakeRemaining = (1000 + 1000 + 1000) - penalty |
| 135 | + const expTotalBondedRemaining = expDelegatedStakeRemaining |
| 136 | + const tokenEndSupply = await token.totalSupply.call() |
| 137 | + const finderFeeAmount = await jobsManager.finderFee.call() |
| 138 | + const finderFee = Math.floor((penalty * finderFeeAmount) / 1000000) |
| 139 | + const burned = tokenStartSupply.sub(tokenEndSupply).toNumber() |
| 140 | + const trans = await bondingManager.getDelegator(transcoder) |
| 141 | + |
| 142 | + assert.isNotOk(await bondingManager.isActiveTranscoder(transcoder, currentRound), "transcoder should be inactive") |
| 143 | + assert.equal(await bondingManager.transcoderStatus(transcoder), 0, "transcoder should not be registered") |
| 144 | + assert.equal(trans[0], expTransStakeRemaining, "wrong transcoder stake remaining") |
| 145 | + assert.equal(burned, penalty - finderFee, "wrong amount burned") |
| 146 | + |
| 147 | + // Check that the finder was rewarded |
| 148 | + assert.equal(await token.balanceOf(watcher), finderFee, "wrong finder fee") |
| 149 | + |
| 150 | + // Check that the broadcaster was refunded |
| 151 | + assert.equal((await jobsManager.getJob(0))[8], 0, "job escrow should be 0") |
| 152 | + assert.equal((await jobsManager.broadcasters.call(broadcaster))[0], 1000) |
| 153 | + |
| 154 | + // Check that the total stake for the round is updated |
| 155 | + // activeTranscoderSet.call(round) only returns the active stake and not the array of transcoder addresses |
| 156 | + // because Solidity does not return nested arrays in structs |
| 157 | + assert.equal(await bondingManager.activeTranscoderSet.call(currentRound), 0, "wrong active stake remaining") |
| 158 | + |
| 159 | + // Check that the total tokens bonded is updated |
| 160 | + assert.equal(await bondingManager.getTotalBonded(), expTotalBondedRemaining, "wrong total bonded amount") |
| 161 | + }) |
| 162 | +}) |
0 commit comments