Merge pull request #1288 from nspcc-dev/fix/utxo

rpc: adjust `getutxotransfers` RPC
This commit is contained in:
Roman Khimov 2020-08-07 19:08:46 +03:00 committed by GitHub
commit ffbdcb202f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 101 deletions

View file

@ -609,13 +609,10 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
} }
// Process TX outputs. // Process TX outputs.
if err := processOutputs(tx, cache); err != nil { if err := processOutputs(tx, block, cache); err != nil {
return err return err
} }
var pseudoSender util.Uint160
var gasTotal, neoTotal util.Fixed8
// Process TX inputs that are grouped by previous hash. // Process TX inputs that are grouped by previous hash.
for _, inputs := range transaction.GroupInputsByPrevHash(tx.Inputs) { for _, inputs := range transaction.GroupInputsByPrevHash(tx.Inputs) {
prevHash := inputs[0].PrevHash prevHash := inputs[0].PrevHash
@ -623,7 +620,7 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
if err != nil { if err != nil {
return err return err
} }
for i, input := range inputs { for _, input := range inputs {
if len(unspent.States) <= int(input.PrevIndex) { if len(unspent.States) <= int(input.PrevIndex) {
return fmt.Errorf("bad input: %s/%d", input.PrevHash.StringLE(), input.PrevIndex) return fmt.Errorf("bad input: %s/%d", input.PrevHash.StringLE(), input.PrevIndex)
} }
@ -633,13 +630,8 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
unspent.States[input.PrevIndex].State |= state.CoinSpent unspent.States[input.PrevIndex].State |= state.CoinSpent
unspent.States[input.PrevIndex].SpendHeight = block.Index unspent.States[input.PrevIndex].SpendHeight = block.Index
prevTXOutput := &unspent.States[input.PrevIndex].Output prevTXOutput := &unspent.States[input.PrevIndex].Output
if i == 0 { if err := processTransfer(cache, tx, block, prevTXOutput, true); err != nil {
pseudoSender = prevTXOutput.ScriptHash return err
}
if prevTXOutput.AssetID.Equals(GoverningTokenID()) {
neoTotal += prevTXOutput.Amount
} else if prevTXOutput.AssetID.Equals(UtilityTokenID()) {
gasTotal += prevTXOutput.Amount
} }
account, err := cache.GetAccountStateOrNew(prevTXOutput.ScriptHash) account, err := cache.GetAccountStateOrNew(prevTXOutput.ScriptHash)
if err != nil { if err != nil {
@ -690,10 +682,6 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
} }
} }
if err := bc.processTransfer(cache, pseudoSender, tx, block, neoTotal, gasTotal); err != nil {
return err
}
// Process the underlying type of the TX. // Process the underlying type of the TX.
switch t := tx.Data.(type) { switch t := tx.Data.(type) {
case *transaction.RegisterTX: case *transaction.RegisterTX:
@ -939,67 +927,32 @@ func appendSingleTransfer(cache *dao.Cached, acc util.Uint160, tr *state.Transfe
} }
// processTransfer processes single UTXO transfer. Totals is a slice of neo (0) and gas (1) total transfer amount. // processTransfer processes single UTXO transfer. Totals is a slice of neo (0) and gas (1) total transfer amount.
func (bc *Blockchain) processTransfer(cache *dao.Cached, from util.Uint160, tx *transaction.Transaction, b *block.Block, func processTransfer(cache *dao.Cached, tx *transaction.Transaction, b *block.Block, out *transaction.Output,
neoTotal, gasTotal util.Fixed8) error { isSent bool) error {
isGoverning := out.AssetID.Equals(GoverningTokenID())
fromIndex, err := cache.GetNextTransferBatch(from) if !isGoverning && !out.AssetID.Equals(UtilityTokenID()) {
return nil
}
tr := &state.Transfer{
IsGoverning: isGoverning,
IsSent: isSent,
Amount: int64(out.Amount),
Block: b.Index,
Timestamp: b.Timestamp,
Tx: tx.Hash(),
}
index, err := cache.GetNextTransferBatch(out.ScriptHash)
if err != nil { if err != nil {
return err return err
} }
for i := range tx.Outputs { isBig, err := cache.AppendTransfer(out.ScriptHash, index, tr)
isGoverning := tx.Outputs[i].AssetID.Equals(GoverningTokenID()) if err != nil {
if !isGoverning && !tx.Outputs[i].AssetID.Equals(UtilityTokenID()) { return err
continue
}
if !from.Equals(tx.Outputs[i].ScriptHash) {
tr := &state.Transfer{
IsGoverning: isGoverning,
From: from,
To: tx.Outputs[i].ScriptHash,
Amount: int64(tx.Outputs[i].Amount),
Block: b.Index,
Timestamp: b.Timestamp,
Tx: tx.Hash(),
}
isBig, err := cache.AppendTransfer(from, fromIndex, tr)
if err != nil {
return err
} else if isBig {
fromIndex++
}
if err := appendSingleTransfer(cache, tx.Outputs[i].ScriptHash, tr); err != nil {
return err
}
}
if isGoverning {
neoTotal -= tx.Outputs[i].Amount
} else {
gasTotal -= tx.Outputs[i].Amount
}
} }
for i, amount := range []util.Fixed8{neoTotal, gasTotal} { if isBig {
if amount > 0 { return cache.PutNextTransferBatch(out.ScriptHash, index+1)
tr := &state.Transfer{
IsGoverning: i == 0,
From: from,
Amount: int64(amount),
Block: b.Index,
Timestamp: b.Timestamp,
Tx: tx.Hash(),
}
isBig, err := cache.AppendTransfer(from, fromIndex, tr)
if err != nil {
return err
} else if isBig {
fromIndex++
}
if err := appendSingleTransfer(cache, util.Uint160{}, tr); err != nil {
return err
}
}
} }
return cache.PutNextTransferBatch(from, fromIndex) return nil
} }
func parseUint160(addr []byte) util.Uint160 { func parseUint160(addr []byte) util.Uint160 {
@ -1126,7 +1079,7 @@ func (bc *Blockchain) LastBatch() *storage.MemBatch {
} }
// processOutputs processes transaction outputs. // processOutputs processes transaction outputs.
func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error { func processOutputs(tx *transaction.Transaction, b *block.Block, dao *dao.Cached) error {
for index, output := range tx.Outputs { for index, output := range tx.Outputs {
account, err := dao.GetAccountStateOrNew(output.ScriptHash) account, err := dao.GetAccountStateOrNew(output.ScriptHash)
if err != nil { if err != nil {
@ -1143,6 +1096,9 @@ func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error {
if err = processTXWithValidatorsAdd(&output, account, dao); err != nil { if err = processTXWithValidatorsAdd(&output, account, dao); err != nil {
return err return err
} }
if err = processTransfer(dao, tx, b, &output, false); err != nil {
return err
}
} }
return nil return nil
} }

View file

@ -6,16 +6,14 @@ import (
) )
// TransferSize is a size of a marshaled Transfer struct in bytes. // TransferSize is a size of a marshaled Transfer struct in bytes.
const TransferSize = 1 + util.Uint160Size*2 + 8 + 4 + 4 + util.Uint256Size const TransferSize = 2 + 8 + 4 + 4 + util.Uint256Size
// Transfer represents a single Transfer event. // Transfer represents a single Transfer event.
type Transfer struct { type Transfer struct {
// IsGoverning is true iff transfer is for neo token. // IsGoverning is true iff transfer is for neo token.
IsGoverning bool IsGoverning bool
// Address is the address of the sender. // IsSent is true iff UTXO used in the input.
From util.Uint160 IsSent bool
// To is the address of the receiver.
To util.Uint160
// Amount is the amount of tokens transferred. // Amount is the amount of tokens transferred.
// It is negative when tokens are sent and positive if they are received. // It is negative when tokens are sent and positive if they are received.
Amount int64 Amount int64
@ -31,21 +29,19 @@ type Transfer struct {
// Note: change TransferSize constant when changing this function. // Note: change TransferSize constant when changing this function.
func (t *Transfer) EncodeBinary(w *io.BinWriter) { func (t *Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.Tx[:]) w.WriteBytes(t.Tx[:])
w.WriteBytes(t.From[:])
w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block) w.WriteU32LE(t.Block)
w.WriteU32LE(t.Timestamp) w.WriteU32LE(t.Timestamp)
w.WriteU64LE(uint64(t.Amount)) w.WriteU64LE(uint64(t.Amount))
w.WriteBool(t.IsGoverning) w.WriteBool(t.IsGoverning)
w.WriteBool(t.IsSent)
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
func (t *Transfer) DecodeBinary(r *io.BinReader) { func (t *Transfer) DecodeBinary(r *io.BinReader) {
r.ReadBytes(t.Tx[:]) r.ReadBytes(t.Tx[:])
r.ReadBytes(t.From[:])
r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE() t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU32LE() t.Timestamp = r.ReadU32LE()
t.Amount = int64(r.ReadU64LE()) t.Amount = int64(r.ReadU64LE())
t.IsGoverning = r.ReadBool() t.IsGoverning = r.ReadBool()
t.IsSent = r.ReadBool()
} }

View file

@ -7,7 +7,6 @@ type UTXO struct {
Index uint32 `json:"block_index"` Index uint32 `json:"block_index"`
Timestamp uint32 `json:"timestamp"` Timestamp uint32 `json:"timestamp"`
TxHash util.Uint256 `json:"txid"` TxHash util.Uint256 `json:"txid"`
Address util.Uint160 `json:"transfer_address"`
Amount int64 `json:"amount,string"` Amount int64 `json:"amount,string"`
} }

View file

@ -541,24 +541,16 @@ func (s *Server) getUTXOTransfers(ps request.Params) (interface{}, *response.Err
if !tr.IsGoverning { if !tr.IsGoverning {
assetID = core.UtilityTokenID() assetID = core.UtilityTokenID()
} }
a, ok := sent[assetID] m := recv
if ok && tr.From.Equals(addr) && !tr.To.Equals(addr) { if tr.IsSent {
a.Transactions = append(a.Transactions, result.UTXO{ m = sent
Index: tr.Block,
Timestamp: tr.Timestamp,
TxHash: tr.Tx,
Address: tr.To,
Amount: tr.Amount,
})
a.TotalAmount += tr.Amount
} }
a, ok = recv[assetID] a, ok := m[assetID]
if ok && tr.To.Equals(addr) && !tr.From.Equals(addr) { if ok {
a.Transactions = append(a.Transactions, result.UTXO{ a.Transactions = append(a.Transactions, result.UTXO{
Index: tr.Block, Index: tr.Block,
Timestamp: tr.Timestamp, Timestamp: tr.Timestamp,
TxHash: tr.Tx, TxHash: tr.Tx,
Address: tr.From,
Amount: tr.Amount, Amount: tr.Amount,
}) })
a.TotalAmount += tr.Amount a.TotalAmount += tr.Amount

View file

@ -1285,21 +1285,36 @@ func checkTransfers(t *testing.T, e *executor, acc interface{}, asset string, st
require.Equal(t, res.Address, "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs") require.Equal(t, res.Address, "AKkkumHbBipZ46UMZJoFynJMXzSRnBvKcs")
// transfer from multisig address to us // transfer from multisig address to us
u := getUTXOForBlock(res, false, asset, 1) u := getUTXOForBlock(res, false, "neo", 1)
if start <= 1 && (stop == 0 || stop >= 1) && (asset == "neo" || asset == "") { if start <= 1 && (stop == 0 || stop >= 1) && (asset == "neo" || asset == "") {
require.NotNil(t, u) require.NotNil(t, u)
require.Equal(t, "be48d3a3f5d10013ab9ffee489706078714f1ea2", u.Address.StringBE())
require.EqualValues(t, int64(util.Fixed8FromInt64(99999000)), u.Amount) require.EqualValues(t, int64(util.Fixed8FromInt64(99999000)), u.Amount)
} else { } else {
require.Nil(t, u) require.Nil(t, u)
} }
// gas claim
u = getUTXOForBlock(res, false, "gas", 203)
if start <= 203 && (stop == 0 || stop >= 203) && (asset == "gas" || asset == "") {
require.NotNil(t, u)
require.EqualValues(t, int64(160798392000), u.Amount)
} else {
require.Nil(t, u)
}
// transfer from us to another validator // transfer from us to another validator
u = getUTXOForBlock(res, true, asset, 206) u = getUTXOForBlock(res, true, "neo", 206)
if start <= 206 && (stop == 0 || stop >= 206) && (asset == "neo" || asset == "") { if start <= 206 && (stop == 0 || stop >= 206) && (asset == "neo" || asset == "") {
require.NotNil(t, u) require.NotNil(t, u)
require.Equal(t, "9fbf833320ef6bc52ddee1fe6f5793b42e9b307e", u.Address.StringBE()) require.EqualValues(t, int64(util.Fixed8FromInt64(99999000)), u.Amount)
require.EqualValues(t, int64(util.Fixed8FromInt64(1000)), u.Amount) } else {
require.Nil(t, u)
}
u = getUTXOForBlock(res, false, "neo", 206)
if start <= 206 && (stop == 0 || stop >= 206) && (asset == "neo" || asset == "") {
require.NotNil(t, u)
require.EqualValues(t, int64(util.Fixed8FromInt64(99998000)), u.Amount)
} else { } else {
require.Nil(t, u) require.Nil(t, u)
} }