Skip to content

Commit

Permalink
UPD(utxo): recover used inputs in forked recovery
Browse files Browse the repository at this point in the history
  • Loading branch information
soooooot committed Nov 1, 2018
1 parent d51aa54 commit 576b6d5
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 57 deletions.
109 changes: 59 additions & 50 deletions internal/coreclient/DBPMsg/go/lws/lws.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions internal/db/model/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type Tx struct {
Inputs []byte `gorm:"type:blob;"`
// 输出金额
Amount int64
// 找零金额
Change int64
// 网络交易费
Fee int64
// 接收地址 1 字节前缀 + 32 字节地址
Expand Down
121 changes: 121 additions & 0 deletions internal/db/service/utxo/RecoverUsedInputs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package utxo

import (
"log"

model "github.com/FissionAndFusion/lws/internal/db/model"
sqlbuilder "github.com/huandu/go-sqlbuilder"
"github.com/jinzhu/gorm"
)

type Input struct {
Hash []byte
N uint8
}

func RecoverUsedInputs(height uint32, connection *gorm.DB) error {
var txs []model.Tx
results := connection.Select("inputs").Where("block_height >= ?", height).Find(&txs)
if results.Error != nil {
return results.Error
}

// get inputs
var inputs []*Input
for _, tx := range txs {
inputLen := len(tx.Inputs)
for i := 0; i < inputLen; i += 33 {
input := &Input{
Hash: tx.Inputs[i : i+32],
N: tx.Inputs[i+32],
}
inputs = append(inputs, input)
}
}

// hash --> n
inputOutMap := make(map[[32]byte]uint8)

// get inputs txs (ignore height >= n)
txHashList := make([][]byte, len(inputs))
for i, input := range inputs {
var hashArr [32]byte
txHashList[i] = input.Hash
copy(hashArr[:], input.Hash)
inputOutMap[hashArr] = input.N
// log.Printf("map hash [%v] out [%d]", input.Hash, input.N)
}

log.Printf("recover: inputs query done [%d]", len(inputs))

var inputTxs []model.Tx
results = connection.Where("hash in (?) and block_height < ?", txHashList, height).Find(&inputTxs)
if results.Error != nil {
return results.Error
}

utxos := make([]*model.Utxo, len(inputTxs))
// genereate utxo from inputs-txs
for i, inputTx := range inputTxs {
var hashArr [32]byte
copy(hashArr[:], inputTx.Hash)
out, ok := inputOutMap[hashArr]
if !ok {
log.Printf("should not happen: recovery tx-hash[%s] not found", inputTx.Hash)
continue
}

var dest []byte
var amount int64
if out == 0 {
dest = inputTx.SendTo
amount = inputTx.Amount
} else {
dest = inputTx.Sender
amount = inputTx.Change
}

utxo := &model.Utxo{
TxHash: inputTx.Hash,
Destination: dest,
Amount: amount,
BlockHeight: inputTx.BlockHeight,
Out: out,
}
// log.Printf("insert utxo#%d: [%v]", i, utxo)
utxos[i] = utxo
}

if len(utxos) <= 0 {
log.Printf("no utxo should be recovered")
return nil
}

log.Printf("prepare bulk insert [%d]", len(utxos))
// bulk create recover utxos
ib := sqlbuilder.NewInsertBuilder()
ib.InsertInto("utxo")
ib.Cols("created_at", "updated_at", "tx_hash", "destination", "amount", "block_height", "`out`")

for _, item := range utxos {
// log.Printf("insert utxo: [%v]", item)
ib.Values(
sqlbuilder.Raw("now()"),
sqlbuilder.Raw("now()"),
item.TxHash,
item.Destination,
item.Amount,
item.BlockHeight,
item.Out,
)
}

sql, args := ib.Build()
// log.Printf("sql: [%s] arg[%s]", sql, args)
_, err := connection.CommonDB().Exec(sql, args...)
if err != nil {
return err
}

return nil
}
55 changes: 55 additions & 0 deletions internal/db/service/utxo/RecoverUsedInputs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package utxo

import (
"bytes"
"encoding/hex"
"github.com/FissionAndFusion/lws/internal/db"
"github.com/FissionAndFusion/lws/internal/db/model"
"testing"

"github.com/FissionAndFusion/lws/test/helper"
)

func TestRecoverUsedInputs(t *testing.T) {
helper.ResetDb()
helper.LoadTestSeed("seedBasic.sql")
helper.LoadTestSeed("seedBasicPlus.sql")

connection := db.GetConnection()

err := RecoverUsedInputs(3, connection)
if err != nil {
t.Fatalf("recover used inputs error [%s]", err)
}

dest, _ := hex.DecodeString("020000000000000000000000000000000000000000000000000000000000000002")
hash1, _ := hex.DecodeString("0003000000000000000000000000000000000000000000000000000000000001")

utxo1 := &model.Utxo{}
res := connection.Where("tx_hash = ? and `out` = ?", hash1, 0).Take(utxo1)
if res.Error != nil {
t.Fatalf("take utxo1 failed [%s]", res.Error)
}

if bytes.Compare(dest, utxo1.Destination) != 0 {
t.Errorf("utxo1.destination expect [%v], but [%v]", hash1, utxo1.Destination)
}
if utxo1.Amount != 15000100 {
t.Errorf("utxo1.destination expect 15000100, but [%d]", utxo1.Amount)
}

hash2, _ := hex.DecodeString("0003000000000000000000000000000000000000000000000000000000000002")
utxo2 := &model.Utxo{}
res = connection.Where("tx_hash = ? and `out` = ?", hash2, 1).Take(utxo2)
if res.Error != nil {
t.Fatalf("take utxo1 failed [%s]", res.Error)
}

if bytes.Compare(dest, utxo2.Destination) != 0 {
t.Errorf("utxo1.destination expect [%v], but [%v]", hash1, utxo1.Destination)
}
if utxo2.Amount != 6999900 {
t.Errorf("utxo1.destination expect 6999900, but [%d]", utxo2.Amount)
}

}
Loading

0 comments on commit 576b6d5

Please sign in to comment.