From 7f46fbab0d9eab214587d7cd67e0d9d30b715042 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Fri, 14 Feb 2020 16:33:36 +0100 Subject: [PATCH] Fix incorrect order of utxos --- db/rocksdb.go | 49 +++++++++++-- db/rocksdb_test.go | 169 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 7 deletions(-) diff --git a/db/rocksdb.go b/db/rocksdb.go index defe0c29..03634b53 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -1849,7 +1849,28 @@ func (d *RocksDB) ComputeInternalStateColumnStats(stopCompute chan os.Signal) er return nil } -func (d *RocksDB) fixUtxo(addrDesc bchain.AddressDescriptor, ba *AddrBalance) (bool, error) { +func reorderUtxo(utxos []Utxo, index int) { + var from, to int + for from = index; from >= 0; from-- { + if !bytes.Equal(utxos[from].BtxID, utxos[index].BtxID) { + break + } + } + from++ + for to = index + 1; to < len(utxos); to++ { + if !bytes.Equal(utxos[to].BtxID, utxos[index].BtxID) { + break + } + } + toSort := utxos[from:to] + sort.SliceStable(toSort, func(i, j int) bool { + return toSort[i].Vout < toSort[j].Vout + }) + +} + +func (d *RocksDB) fixUtxo(addrDesc bchain.AddressDescriptor, ba *AddrBalance) (bool, bool, error) { + reorder := false var checksum big.Int var prevUtxo *Utxo for i := range ba.Utxos { @@ -1857,7 +1878,8 @@ func (d *RocksDB) fixUtxo(addrDesc bchain.AddressDescriptor, ba *AddrBalance) (b checksum.Add(&checksum, &utxo.ValueSat) if prevUtxo != nil { if prevUtxo.Vout > utxo.Vout && *(*int)(unsafe.Pointer(&utxo.BtxID[0])) == *(*int)(unsafe.Pointer(&prevUtxo.BtxID[0])) && bytes.Equal(utxo.BtxID, prevUtxo.BtxID) { - glog.Error("FixUtxo: addrDesc ", addrDesc, ", needs reorder") + reorderUtxo(ba.Utxos, i) + reorder = true } } prevUtxo = utxo @@ -1899,7 +1921,7 @@ func (d *RocksDB) fixUtxo(addrDesc bchain.AddressDescriptor, ba *AddrBalance) (b return nil }) if err != nil { - return false, err + return false, false, err } fixed := false if checksumFromTxs.Cmp(&ba.BalanceSat) == 0 { @@ -1916,13 +1938,23 @@ func (d *RocksDB) fixUtxo(addrDesc bchain.AddressDescriptor, ba *AddrBalance) (b } wb.Destroy() if err != nil { - return false, errors.Errorf("balance %s, checksum %s, from txa %s, txs %d, error storing fixed utxos %v", ba.BalanceSat.String(), checksum.String(), checksumFromTxs.String(), ba.Txs, err) + return false, false, errors.Errorf("balance %s, checksum %s, from txa %s, txs %d, error storing fixed utxos %v", ba.BalanceSat.String(), checksum.String(), checksumFromTxs.String(), ba.Txs, err) } fixed = true } - return fixed, errors.Errorf("balance %s, checksum %s, from txa %s, txs %d", ba.BalanceSat.String(), checksum.String(), checksumFromTxs.String(), ba.Txs) + return fixed, false, errors.Errorf("balance %s, checksum %s, from txa %s, txs %d", ba.BalanceSat.String(), checksum.String(), checksumFromTxs.String(), ba.Txs) + } else if reorder { + wb := gorocksdb.NewWriteBatch() + err := d.storeBalances(wb, map[string]*AddrBalance{string(addrDesc): ba}) + if err == nil { + err = d.db.Write(d.wo, wb) + } + wb.Destroy() + if err != nil { + return false, false, errors.Errorf("error storing reordered utxos %v", err) + } } - return false, nil + return false, reorder, nil } // FixUtxos checks and fixes possible @@ -1968,13 +2000,16 @@ func (d *RocksDB) FixUtxos(stop chan os.Signal) error { errorsCount++ continue } - fixed, err := d.fixUtxo(addrDesc, ba) + fixed, reordered, err := d.fixUtxo(addrDesc, ba) if err != nil { errorsCount++ glog.Error("FixUtxos: row ", row, ", addrDesc ", addrDesc, ", error ", err, ", fixed ", fixed) if fixed { fixedCount++ } + } else if reordered { + glog.Error("FixUtxos: row ", row, ", addrDesc ", addrDesc, " reordered") + fixedCount++ } } seekKey = append([]byte{}, addrDesc...) diff --git a/db/rocksdb_test.go b/db/rocksdb_test.go index 75ae1c52..1a789f8b 100644 --- a/db/rocksdb_test.go +++ b/db/rocksdb_test.go @@ -1314,6 +1314,175 @@ func TestAddrBalance_utxo_methods(t *testing.T) { } +func Test_reorderUtxo(t *testing.T) { + utxos := []Utxo{ + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 3, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 1, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 2, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 1, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB2T1), + Vout: 2, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB2T1), + Vout: 0, + }, + } + tests := []struct { + name string + utxos []Utxo + index int + want []Utxo + }{ + { + name: "middle", + utxos: utxos, + index: 4, + want: []Utxo{ + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 3, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 1, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 1, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 2, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB2T1), + Vout: 2, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB2T1), + Vout: 0, + }, + }, + }, + { + name: "start", + utxos: utxos, + index: 1, + want: []Utxo{ + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 1, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 3, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 1, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 2, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB2T1), + Vout: 2, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB2T1), + Vout: 0, + }, + }, + }, + { + name: "end", + utxos: utxos, + index: 6, + want: []Utxo{ + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 1, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T1), + Vout: 3, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 1, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB1T2), + Vout: 2, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB2T1), + Vout: 0, + }, + { + BtxID: hexToBytes(dbtestdata.TxidB2T1), + Vout: 2, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reorderUtxo(tt.utxos, tt.index) + if !reflect.DeepEqual(tt.utxos, tt.want) { + t.Errorf("reorderUtxo %s, got %+v, want %+v", tt.name, tt.utxos, tt.want) + } + }) + } +} + func TestRocksTickers(t *testing.T) { d := setupRocksDB(t, &testBitcoinParser{ BitcoinParser: bitcoinTestnetParser(),