|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +commentIssueId: 38 |
| 4 | +--- |
| 5 | + |
| 6 | +Gavin Andresen of bitcoin fame posted |
| 7 | +[a github gist](https://gist.github.com/gavinandresen/e20c3b5a1d4b97f79ac2) |
| 8 | +a while ago, about using IBLTs to send block transaction summaries across |
| 9 | +the bitcoin network. |
| 10 | + |
| 11 | +## About IBLT ## |
| 12 | + |
| 13 | +The IBLT structure is fairly simple: create a counting bloom filter, |
| 14 | +and attach to each bucket the contents of the thing you're filtering |
| 15 | +(xoring in each time). If the IBLT is fairly empty, you'll find |
| 16 | +buckets which only have 1 thing in them, and thus recover the things |
| 17 | +which were put in (this is the "invertible" part). When you remove |
| 18 | +those, you might find more buckets now have 1 thing in them, etc. The |
| 19 | +[paper](http://arxiv.org/pdf/1101.2245.pdf) is worth reading, but this |
| 20 | +should give you the idea. |
| 21 | + |
| 22 | +Here's a simplified example. Consider the following buckets: |
| 23 | + |
| 24 | + count: 1 |
| 25 | + id: 0xf00 |
| 26 | + index: 0 |
| 27 | + frag: Feed Me |
| 28 | + |
| 29 | + count: 1 |
| 30 | + id: 0xf00 |
| 31 | + index: 1 |
| 32 | + frag: Seymore! |
| 33 | + |
| 34 | + count: 2 |
| 35 | + id: 0xf0f |
| 36 | + index: 0 |
| 37 | + frag: 0x70x170x100xB0x90x1B0x10x53 |
| 38 | + |
| 39 | + count: 1 |
| 40 | + id: 0x00f |
| 41 | + index: 0 |
| 42 | + frag: I love |
| 43 | + |
| 44 | +Here each "transaction" has 2 parts (index 0 and index 1), we can see |
| 45 | +both parts of id 0xf00 exposed in the first two buckets. The result |
| 46 | +is "Feed Me Seymore!". Now we've recovered that, we remove it, and it |
| 47 | +turns out that fragment 1 is also in that third bucket, so we subtract |
| 48 | +1 from the counter and XOR out the other values, giving: |
| 49 | + |
| 50 | + count: 0 |
| 51 | + id: 0 |
| 52 | + index: 0 |
| 53 | + frag: |
| 54 | + |
| 55 | + count: 0 |
| 56 | + id: 0 |
| 57 | + index: 0 |
| 58 | + frag: |
| 59 | + |
| 60 | + count: 1 |
| 61 | + id: 0x00f |
| 62 | + index: 1 |
| 63 | + frag: Triffids |
| 64 | + |
| 65 | + count: 1 |
| 66 | + id: 0x00f |
| 67 | + index: 0 |
| 68 | + frag: I love |
| 69 | + |
| 70 | +Now we can extract id 0x00f, which is "I love Triffids". |
| 71 | + |
| 72 | +### Subtracting IBLTs ### |
| 73 | + |
| 74 | +The clever bit comes by realizing that if I create an IBLT with |
| 75 | +everything in my set, and you create an IBLT with everything in your |
| 76 | +set, it's trivial to subtract my IBLT from your IBLT and get an IBLT |
| 77 | +of the set differences. Which is great, because all bloom filters |
| 78 | +become useless when they clog up, but if our sets are very similar, |
| 79 | +the subtraction result gives a nice sparse IBLT. And |
| 80 | +[here's the paper](http://conferences.sigcomm.org/sigcomm/2011/papers/sigcomm/p218.pdf). |
| 81 | + |
| 82 | +## Using IBLTs for Bitcoin ## |
| 83 | + |
| 84 | +Gavin suggested some ways to adapt an IBLT to the blockchain problem: |
| 85 | + |
| 86 | +1. Set a size of 1M for the whole thing. |
| 87 | +2. Make each IBLT bucket look like: |
| 88 | + struct entry { |
| 89 | + u32 count; |
| 90 | + struct keySum { |
| 91 | + u8 id[6]; |
| 92 | + u16 index; |
| 93 | + u8 frag[8]; |
| 94 | + } key; |
| 95 | + } |
| 96 | + This gives 52428 buckets for a 1MB table. |
| 97 | + |
| 98 | +3. Generate a 48-bit id for each transaction and fragment it into |
| 99 | + 64-bits at a time, with index incremented for each fragment. This |
| 100 | + means the average transaction (263 bytes) uses about 32 of these. |
| 101 | + |
| 102 | +## Testing It ## |
| 103 | + |
| 104 | +For my experiment, I used 4 hash functions for each bloom entry, and |
| 105 | +just make the 48-bit id the first 48 bits of the SHA. I used a simple |
| 106 | +shell script to pull out the last 10000 bitcoin transactions. |
| 107 | + |
| 108 | +### Recovering Transactions ### |
| 109 | + |
| 110 | +Let's first look at how many transactions we can put in such table and |
| 111 | +pull them out again. This reflects the simple case where the miner |
| 112 | +has N transactions we don't have (and we have none they don't have). |
| 113 | +For each transaction, we just have to find a standalone copy of each |
| 114 | +fragment; since each fragment is in 4 separate places, removing a |
| 115 | +transaction as we find it may leave other fragments exposed so we can |
| 116 | +make more progress. |
| 117 | + |
| 118 | +The result turns out to be a cliff: with 300 transactions we can |
| 119 | +extract all the transactions 100% of the time, with 320 it's down to |
| 120 | +0% (this is only 10 runs though). We only care about full recovery, |
| 121 | +because if we can't get one transaction, we fall back to getting the |
| 122 | +whole block: |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | +### Eliminating not-present Transactions ### |
| 127 | + |
| 128 | +Now, reality is a bit more complex. We will have some transactions |
| 129 | +which are *not* in the block (I'll call these "our" transactions), as |
| 130 | +well as missing some which are ("their" transactions). Indeed, if the |
| 131 | +network is working perfectly, we should have all the transactions in |
| 132 | +the block, and also any which have reached us while the block was |
| 133 | +being relayed to us, so we expect "our" transactions to dominate. |
| 134 | + |
| 135 | +And it turns out that it's much easier to detect transactions which |
| 136 | +aren't in the block than those that are; we only need to find a single |
| 137 | +standalone fragment with count -1, and we can be fairly confident that |
| 138 | +it means we've found one of our transactions and should remove the |
| 139 | +whole thing. Finding a single exposed fragment for a transaction like |
| 140 | +this is far more likely than finding all fragments of a transaction |
| 141 | +exposed, and indeed our cliff for these is far higher, around 3300: |
| 142 | + |
| 143 | + |
| 144 | + |
| 145 | +Finally, here's a heat map of interaction between the two, as the |
| 146 | +number of "their" transactions we need to recover and the number of |
| 147 | +"our" transactions we should remove varies. It really needs more data |
| 148 | +points, but you get the idea: |
| 149 | + |
| 150 | + |
| 151 | + |
| 152 | +## Testing Some Variants ## |
| 153 | + |
| 154 | +There are three obvious things to try: |
| 155 | +1. Increase the size of the slice in each IBLT bucket. |
| 156 | +2. Reduce the size of the IBLT from 1M. |
| 157 | +3. Add a hashSum field to each bucket as suggested by the paper. |
| 158 | + |
| 159 | +### Larger slices ### |
| 160 | + |
| 161 | +Increasing the data per bucket from 8 to 64 bytes seems like a no-brainer: |
| 162 | +instead of 50% overhead for IBLT data, we're down to 12%. Obviously we |
| 163 | +have fewer buckets, but it seems like a net win: |
| 164 | + |
| 165 | + |
| 166 | + |
| 167 | +If we make the buckets *too* large (eg. 256 bytes), we lose as expected, |
| 168 | +as most space is wasted: |
| 169 | + |
| 170 | + |
| 171 | + |
| 172 | +### Smaller IBLTs ### |
| 173 | + |
| 174 | +As expected it's not linear, but it's close enough: with 102k, we can |
| 175 | +recover 30-35 transactions, and with 2M it's about 500-520 transactions. |
| 176 | + |
| 177 | +Here's the heatmap with 30k (and 64-byte slices); this will almost |
| 178 | +certainly enough for the immediate future: |
| 179 | + |
| 180 | + |
| 181 | + |
| 182 | +### A Byte of HashSum ### |
| 183 | + |
| 184 | +This is suggested in the original paper to provide more resilience |
| 185 | +against false positives, but I only saw this once, even with the very |
| 186 | +limited checking my program does that a transaction is well-formed. |
| 187 | +(I found it because my code initially didn't handle it, and got stuck |
| 188 | +on one run). |
| 189 | + |
| 190 | +Once we check the 48-bit id field, which is based on a cryptographic |
| 191 | +hash of the transaction, we're already extremely robust without an |
| 192 | +additional hashSum. |
| 193 | + |
| 194 | +## Results and Suggestions ## |
| 195 | + |
| 196 | +8 bytes per slice is too small, larger sizes should be explored. |
| 197 | + |
| 198 | +1M is vast overkill for the current network; as well as making block |
| 199 | +propagation *slower* than the current scheme, it's unnecessary for |
| 200 | +now. I suggest a variable scheme. |
| 201 | + |
| 202 | +Nodes should probably include discarded doublespend transactions in |
| 203 | +their IBLT since it's cheaper to include a transaction for |
| 204 | +consideration than to extract one which we didn't consider. |
| 205 | + |
| 206 | +## Flaws and Future Work ## |
| 207 | + |
| 208 | +Obviously my code could use optimization. Also, my selection of |
| 209 | +transactions was "the first N", which means it's of limited accuracy. |
| 210 | +For example, transaction 613 is a huge 50k transaction, so if it's |
| 211 | +marginal the failure rate jumps there. A more serious analysis would |
| 212 | +try to create standard profiles of transactions. |
| 213 | + |
| 214 | +[Feedback](mailto:rusty@rustcorp.com.au) welcome, as always. |
0 commit comments