diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 6239ad89f..4cae2ba98 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -29,7 +29,7 @@ import ( // Tuning parameters. const ( headerBatchCount = 2000 - version = "0.0.3" + version = "0.0.4" // This one comes from C# code and it's different from the constant used // when creating an asset with Neo.Asset.Create interop call. It looks @@ -479,6 +479,13 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { } if prevTXOutput.AssetID.Equals(GoverningTokenID()) { + account.Unclaimed = append(account.Unclaimed, state.UnclaimedBalance{ + Tx: prevTX.Hash(), + Index: input.PrevIndex, + Start: prevTXHeight, + End: block.Index, + Value: prevTXOutput.Amount, + }) spentCoin.items[input.PrevIndex] = block.Index if err = processTXWithValidatorsSubtract(&prevTXOutput, account, cache); err != nil { return err @@ -570,6 +577,37 @@ func (bc *Blockchain) storeBlock(block *block.Block) error { } break } + + prevTx, _, err := cache.GetTransaction(input.PrevHash) + if err != nil { + return err + } else if int(input.PrevIndex) > len(prevTx.Outputs) { + return errors.New("invalid input in claim") + } + acc, err := cache.GetAccountState(prevTx.Outputs[input.PrevIndex].ScriptHash) + if err != nil { + return err + } + + var changed bool + for i := range acc.Unclaimed { + if acc.Unclaimed[i].Tx == input.PrevHash && acc.Unclaimed[i].Index == input.PrevIndex { + copy(acc.Unclaimed[i:], acc.Unclaimed[i+1:]) + acc.Unclaimed = acc.Unclaimed[:len(acc.Unclaimed)-1] + changed = true + break + } + } + + if !changed { + bc.log.Warn("no spent coin in the account", + zap.String("tx", tx.Hash().StringLE()), + zap.String("input", input.PrevHash.StringLE()), + zap.String("account", acc.ScriptHash.String())) + } else if err := cache.PutAccountState(acc); err != nil { + return err + } + delete(scs.items, input.PrevIndex) if len(scs.items) > 0 { if err = cache.PutSpentCoinState(input.PrevHash, scs); err != nil { diff --git a/pkg/core/state/account.go b/pkg/core/state/account.go index 12522b0d4..cbb07dac0 100644 --- a/pkg/core/state/account.go +++ b/pkg/core/state/account.go @@ -14,6 +14,16 @@ type UnspentBalance struct { Value util.Fixed8 `json:"value"` } +// UnclaimedBalance represents transaction output which was spent and +// can be claimed. +type UnclaimedBalance struct { + Tx util.Uint256 + Index uint16 + Start uint32 + End uint32 + Value util.Fixed8 +} + // UnspentBalances is a slice of UnspentBalance (mostly needed to sort them). type UnspentBalances []UnspentBalance @@ -24,6 +34,7 @@ type Account struct { IsFrozen bool Votes []*keys.PublicKey Balances map[util.Uint256][]UnspentBalance + Unclaimed []UnclaimedBalance } // NewAccount returns a new Account object. @@ -34,6 +45,7 @@ func NewAccount(scriptHash util.Uint160) *Account { IsFrozen: false, Votes: []*keys.PublicKey{}, Balances: make(map[util.Uint256][]UnspentBalance), + Unclaimed: []UnclaimedBalance{}, } } @@ -56,6 +68,8 @@ func (s *Account) DecodeBinary(br *io.BinReader) { } s.Balances[key] = ubs } + + br.ReadArray(&s.Unclaimed) } // EncodeBinary encodes Account to the given BinWriter. @@ -73,6 +87,8 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) { v[i].EncodeBinary(bw) } } + + bw.WriteArray(s.Unclaimed) } // DecodeBinary implements io.Serializable interface. @@ -89,6 +105,24 @@ func (u *UnspentBalance) EncodeBinary(w *io.BinWriter) { u.Value.EncodeBinary(w) } +// DecodeBinary implements io.Serializable interface. +func (u *UnclaimedBalance) DecodeBinary(r *io.BinReader) { + u.Tx.DecodeBinary(r) + u.Index = r.ReadU16LE() + u.Start = r.ReadU32LE() + u.End = r.ReadU32LE() + u.Value.DecodeBinary(r) +} + +// EncodeBinary implements io.Serializable interface. +func (u *UnclaimedBalance) EncodeBinary(w *io.BinWriter) { + u.Tx.EncodeBinary(w) + w.WriteU16LE(u.Index) + w.WriteU32LE(u.Start) + w.WriteU32LE(u.End) + u.Value.EncodeBinary(w) +} + // GetBalanceValues sums all unspent outputs and returns a map of asset IDs to // overall balances. func (s *Account) GetBalanceValues() map[util.Uint256]util.Fixed8 {