forked from TrueCloudLab/neoneo-go
Merge pull request #694 from nspcc-dev/feature/getclaimable
rpc, cli: support gas claim and asset transfer
This commit is contained in:
commit
657f5e46d0
25 changed files with 773 additions and 62 deletions
|
@ -2,14 +2,20 @@ package wallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/rpc/client"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/rpc/request"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/wallet"
|
"github.com/CityOfZion/neo-go/pkg/wallet"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
@ -33,6 +39,14 @@ var (
|
||||||
Name: "decrypt, d",
|
Name: "decrypt, d",
|
||||||
Usage: "Decrypt encrypted keys.",
|
Usage: "Decrypt encrypted keys.",
|
||||||
}
|
}
|
||||||
|
rpcFlag = cli.StringFlag{
|
||||||
|
Name: "rpc, r",
|
||||||
|
Usage: "RPC node address",
|
||||||
|
}
|
||||||
|
timeoutFlag = cli.DurationFlag{
|
||||||
|
Name: "timeout, t",
|
||||||
|
Usage: "Timeout for the operation",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCommands returns 'wallet' command.
|
// NewCommands returns 'wallet' command.
|
||||||
|
@ -41,6 +55,20 @@ func NewCommands() []cli.Command {
|
||||||
Name: "wallet",
|
Name: "wallet",
|
||||||
Usage: "create, open and manage a NEO wallet",
|
Usage: "create, open and manage a NEO wallet",
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "claim",
|
||||||
|
Usage: "claim GAS",
|
||||||
|
Action: claimGas,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
walletPathFlag,
|
||||||
|
rpcFlag,
|
||||||
|
timeoutFlag,
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "address, a",
|
||||||
|
Usage: "Address to claim GAS for",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
Usage: "create a new wallet",
|
Usage: "create a new wallet",
|
||||||
|
@ -112,10 +140,106 @@ func NewCommands() []cli.Command {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "transfer",
|
||||||
|
Usage: "transfer NEO/GAS",
|
||||||
|
UsageText: "transfer --path <path> --from <addr> --to <addr>" +
|
||||||
|
" --amount <amount> --asset [NEO|GAS|<hex-id>]",
|
||||||
|
Action: transferAsset,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
walletPathFlag,
|
||||||
|
rpcFlag,
|
||||||
|
timeoutFlag,
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "from",
|
||||||
|
Usage: "Address to send an asset from",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "to",
|
||||||
|
Usage: "Address to send an asset to",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "amount",
|
||||||
|
Usage: "Amount of asset to send",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "asset",
|
||||||
|
Usage: "Asset ID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func claimGas(ctx *cli.Context) error {
|
||||||
|
wall, err := openWallet(ctx.String("path"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
defer wall.Close()
|
||||||
|
|
||||||
|
addr := ctx.String("address")
|
||||||
|
scriptHash, err := address.StringToUint160(addr)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := wall.GetAccount(scriptHash)
|
||||||
|
if acc == nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", addr), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pass, err := readPassword("Enter password > ")
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
} else if err := acc.Decrypt(pass); err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := getGoContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
info, err := c.GetClaimable(addr)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
} else if info.Unclaimed == 0 || len(info.Spents) == 0 {
|
||||||
|
fmt.Println("Nothing to claim")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var claim transaction.ClaimTX
|
||||||
|
for i := range info.Spents {
|
||||||
|
claim.Claims = append(claim.Claims, transaction.Input{
|
||||||
|
PrevHash: info.Spents[i].Tx,
|
||||||
|
PrevIndex: uint16(info.Spents[i].N),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := &transaction.Transaction{
|
||||||
|
Type: transaction.ClaimType,
|
||||||
|
Data: &claim,
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.AddOutput(&transaction.Output{
|
||||||
|
AssetID: core.UtilityTokenID(),
|
||||||
|
Amount: info.Unclaimed,
|
||||||
|
ScriptHash: scriptHash,
|
||||||
|
})
|
||||||
|
|
||||||
|
_ = acc.SignTx(tx)
|
||||||
|
if err := c.SendRawTransaction(tx); err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(tx.Hash().StringLE())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func addAccount(ctx *cli.Context) error {
|
func addAccount(ctx *cli.Context) error {
|
||||||
wall, err := openWallet(ctx.String("path"))
|
wall, err := openWallet(ctx.String("path"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -249,6 +373,81 @@ func importWallet(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transferAsset(ctx *cli.Context) error {
|
||||||
|
wall, err := openWallet(ctx.String("path"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
defer wall.Close()
|
||||||
|
|
||||||
|
from := ctx.String("from")
|
||||||
|
addr, err := address.StringToUint160(from)
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError("invalid address", 1)
|
||||||
|
}
|
||||||
|
acc := wall.GetAccount(addr)
|
||||||
|
if acc == nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", addr), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
asset, err := getAssetID(ctx.String("asset"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("invalid asset id: %v", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, err := util.Fixed8FromString(ctx.String("amount"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(fmt.Errorf("invalid amount: %v", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pass, err := readPassword("Enter wallet password > ")
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
} else if err := acc.Decrypt(pass); err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := getGoContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c, err := client.New(gctx, ctx.String("rpc"), client.Options{})
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := transaction.NewContractTX()
|
||||||
|
tx.Data = new(transaction.ContractTX)
|
||||||
|
if err := request.AddInputsAndUnspentsToTx(tx, from, asset, amount, c); err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
toAddr, err := address.StringToUint160(ctx.String("to"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
tx.AddOutput(&transaction.Output{
|
||||||
|
AssetID: asset,
|
||||||
|
Amount: amount,
|
||||||
|
ScriptHash: toAddr,
|
||||||
|
Position: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
_ = acc.SignTx(tx)
|
||||||
|
if err := c.SendRawTransaction(tx); err != nil {
|
||||||
|
return cli.NewExitError(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(tx.Hash().StringLE())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGoContext(ctx *cli.Context) (context.Context, func()) {
|
||||||
|
if dur := ctx.Duration("timeout"); dur != 0 {
|
||||||
|
return context.WithTimeout(context.Background(), dur)
|
||||||
|
}
|
||||||
|
return context.Background(), func() {}
|
||||||
|
}
|
||||||
|
|
||||||
func dumpWallet(ctx *cli.Context) error {
|
func dumpWallet(ctx *cli.Context) error {
|
||||||
wall, err := openWallet(ctx.String("path"))
|
wall, err := openWallet(ctx.String("path"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -331,6 +530,18 @@ func openWallet(path string) (*wallet.Wallet, error) {
|
||||||
return wallet.NewWalletFromFile(path)
|
return wallet.NewWalletFromFile(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAssetID(s string) (util.Uint256, error) {
|
||||||
|
s = strings.ToLower(s)
|
||||||
|
switch {
|
||||||
|
case s == "neo":
|
||||||
|
return core.GoverningTokenID(), nil
|
||||||
|
case s == "gas":
|
||||||
|
return core.UtilityTokenID(), nil
|
||||||
|
default:
|
||||||
|
return util.Uint256DecodeStringLE(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newAccountFromWIF(wif string) (*wallet.Account, error) {
|
func newAccountFromWIF(wif string) (*wallet.Account, error) {
|
||||||
// note: NEP2 strings always have length of 58 even though
|
// note: NEP2 strings always have length of 58 even though
|
||||||
// base58 strings can have different lengths even if slice lengths are equal
|
// base58 strings can have different lengths even if slice lengths are equal
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
"github.com/CityOfZion/neo-go/pkg/network"
|
"github.com/CityOfZion/neo-go/pkg/network"
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/request"
|
"github.com/CityOfZion/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
|
@ -49,10 +49,12 @@ func prepareData(t *testing.B) []*transaction.Transaction {
|
||||||
var data []*transaction.Transaction
|
var data []*transaction.Transaction
|
||||||
|
|
||||||
wif := getWif(t)
|
wif := getWif(t)
|
||||||
|
acc, err := wallet.NewAccountFromWIF(wif.S)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
for n := 0; n < t.N; n++ {
|
for n := 0; n < t.N; n++ {
|
||||||
tx := getTX(t, wif)
|
tx := getTX(t, wif)
|
||||||
require.NoError(t, request.SignTx(tx, wif))
|
require.NoError(t, acc.SignTx(tx))
|
||||||
data = append(data, tx)
|
data = append(data, tx)
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -476,16 +476,23 @@ func (s *service) getVerifiedTx(count int) []block.Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) getValidators(txx ...block.Transaction) []crypto.PublicKey {
|
func (s *service) getValidators(txx ...block.Transaction) []crypto.PublicKey {
|
||||||
var pKeys []*keys.PublicKey
|
var (
|
||||||
|
pKeys []*keys.PublicKey
|
||||||
|
err error
|
||||||
|
)
|
||||||
if len(txx) == 0 {
|
if len(txx) == 0 {
|
||||||
pKeys, _ = s.Chain.GetValidators()
|
pKeys, err = s.Chain.GetValidators()
|
||||||
} else {
|
} else {
|
||||||
ntxx := make([]*transaction.Transaction, len(txx))
|
ntxx := make([]*transaction.Transaction, len(txx))
|
||||||
for i := range ntxx {
|
for i := range ntxx {
|
||||||
ntxx[i] = txx[i].(*transaction.Transaction)
|
ntxx[i] = txx[i].(*transaction.Transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
pKeys, _ = s.Chain.GetValidators(ntxx...)
|
pKeys, err = s.Chain.GetValidators(ntxx...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("error while trying to get validators", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
pubs := make([]crypto.PublicKey, len(pKeys))
|
pubs := make([]crypto.PublicKey, len(pKeys))
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
// Tuning parameters.
|
// Tuning parameters.
|
||||||
const (
|
const (
|
||||||
headerBatchCount = 2000
|
headerBatchCount = 2000
|
||||||
version = "0.0.3"
|
version = "0.0.5"
|
||||||
|
|
||||||
// This one comes from C# code and it's different from the constant used
|
// 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
|
// when creating an asset with Neo.Asset.Create interop call. It looks
|
||||||
|
@ -93,6 +93,9 @@ type Blockchain struct {
|
||||||
// Number of headers stored in the chain file.
|
// Number of headers stored in the chain file.
|
||||||
storedHeaderCount uint32
|
storedHeaderCount uint32
|
||||||
|
|
||||||
|
generationAmount []int
|
||||||
|
decrementInterval int
|
||||||
|
|
||||||
// All operations on headerList must be called from an
|
// All operations on headerList must be called from an
|
||||||
// headersOp to be routine safe.
|
// headersOp to be routine safe.
|
||||||
headerList *HeaderHashList
|
headerList *HeaderHashList
|
||||||
|
@ -154,6 +157,9 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L
|
||||||
memPool: mempool.NewMemPool(cfg.MemPoolSize),
|
memPool: mempool.NewMemPool(cfg.MemPoolSize),
|
||||||
keyCache: make(map[util.Uint160]map[string]*keys.PublicKey),
|
keyCache: make(map[util.Uint160]map[string]*keys.PublicKey),
|
||||||
log: log,
|
log: log,
|
||||||
|
|
||||||
|
generationAmount: genAmount,
|
||||||
|
decrementInterval: decrementInterval,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bc.init(); err != nil {
|
if err := bc.init(); err != nil {
|
||||||
|
@ -408,6 +414,7 @@ func (bc *Blockchain) processHeader(h *block.Header, batch storage.Batch, header
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
|
buf.BinWriter.WriteU32LE(0) // sys fee is yet to be calculated
|
||||||
h.EncodeBinary(buf.BinWriter)
|
h.EncodeBinary(buf.BinWriter)
|
||||||
if buf.Err != nil {
|
if buf.Err != nil {
|
||||||
return buf.Err
|
return buf.Err
|
||||||
|
@ -420,13 +427,24 @@ func (bc *Blockchain) processHeader(h *block.Header, batch storage.Batch, header
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bc.GetHeaderHash(int(endHeight)) returns sum of all system fees for blocks up to h.
|
||||||
|
// and 0 if no such block exists.
|
||||||
|
func (bc *Blockchain) getSystemFeeAmount(h util.Uint256) uint32 {
|
||||||
|
_, sf, _ := bc.dao.GetBlock(h)
|
||||||
|
return sf
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: storeBlock needs some more love, its implemented as in the original
|
// TODO: storeBlock needs some more love, its implemented as in the original
|
||||||
// project. This for the sake of development speed and understanding of what
|
// project. This for the sake of development speed and understanding of what
|
||||||
// is happening here, quite allot as you can see :). If things are wired together
|
// is happening here, quite allot as you can see :). If things are wired together
|
||||||
// and all tests are in place, we can make a more optimized and cleaner implementation.
|
// and all tests are in place, we can make a more optimized and cleaner implementation.
|
||||||
func (bc *Blockchain) storeBlock(block *block.Block) error {
|
func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
cache := newCachedDao(bc.dao.store)
|
cache := newCachedDao(bc.dao.store)
|
||||||
if err := cache.StoreAsBlock(block, 0); err != nil {
|
fee := bc.getSystemFeeAmount(block.PrevHash)
|
||||||
|
for _, tx := range block.Transactions {
|
||||||
|
fee += uint32(bc.SystemFee(tx).Int64Value())
|
||||||
|
}
|
||||||
|
if err := cache.StoreAsBlock(block, fee); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,6 +491,13 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if prevTXOutput.AssetID.Equals(GoverningTokenID()) {
|
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
|
spentCoin.items[input.PrevIndex] = block.Index
|
||||||
if err = processTXWithValidatorsSubtract(&prevTXOutput, account, cache); err != nil {
|
if err = processTXWithValidatorsSubtract(&prevTXOutput, account, cache); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -564,6 +589,37 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
|
||||||
}
|
}
|
||||||
break
|
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)
|
delete(scs.items, input.PrevIndex)
|
||||||
if len(scs.items) > 0 {
|
if len(scs.items) > 0 {
|
||||||
if err = cache.PutSpentCoinState(input.PrevHash, scs); err != nil {
|
if err = cache.PutSpentCoinState(input.PrevHash, scs); err != nil {
|
||||||
|
@ -905,7 +961,7 @@ func (bc *Blockchain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
block, err := bc.dao.GetBlock(hash)
|
block, _, err := bc.dao.GetBlock(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -930,7 +986,7 @@ func (bc *Blockchain) GetHeader(hash util.Uint256) (*block.Header, error) {
|
||||||
return tb.Header(), nil
|
return tb.Header(), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
block, err := bc.dao.GetBlock(hash)
|
block, _, err := bc.dao.GetBlock(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1033,6 +1089,48 @@ func (bc *Blockchain) GetConfig() config.ProtocolConfiguration {
|
||||||
return bc.config
|
return bc.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CalculateClaimable calculates the amount of GAS which can be claimed for a transaction with value.
|
||||||
|
// First return value is GAS generated between startHeight and endHeight.
|
||||||
|
// Second return value is GAS returned from accumulated SystemFees between startHeight and endHeight.
|
||||||
|
func (bc *Blockchain) CalculateClaimable(value util.Fixed8, startHeight, endHeight uint32) (util.Fixed8, util.Fixed8, error) {
|
||||||
|
var amount util.Fixed8
|
||||||
|
di := uint32(bc.decrementInterval)
|
||||||
|
|
||||||
|
ustart := startHeight / di
|
||||||
|
if genSize := uint32(len(bc.generationAmount)); ustart < genSize {
|
||||||
|
uend := endHeight / di
|
||||||
|
iend := endHeight % di
|
||||||
|
if uend >= genSize {
|
||||||
|
uend = genSize - 1
|
||||||
|
iend = di
|
||||||
|
} else if iend == 0 {
|
||||||
|
uend--
|
||||||
|
iend = di
|
||||||
|
}
|
||||||
|
|
||||||
|
istart := startHeight % di
|
||||||
|
for ustart < uend {
|
||||||
|
amount += util.Fixed8(di-istart) * util.Fixed8(bc.generationAmount[ustart])
|
||||||
|
ustart++
|
||||||
|
istart = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
amount += util.Fixed8(iend-istart) * util.Fixed8(bc.generationAmount[ustart])
|
||||||
|
}
|
||||||
|
|
||||||
|
if startHeight == 0 {
|
||||||
|
startHeight++
|
||||||
|
}
|
||||||
|
h := bc.GetHeaderHash(int(startHeight - 1))
|
||||||
|
feeStart := bc.getSystemFeeAmount(h)
|
||||||
|
h = bc.GetHeaderHash(int(endHeight - 1))
|
||||||
|
feeEnd := bc.getSystemFeeAmount(h)
|
||||||
|
|
||||||
|
sysFeeTotal := util.Fixed8(feeEnd - feeStart)
|
||||||
|
ratio := value / 100000000
|
||||||
|
return amount * ratio, sysFeeTotal * ratio, nil
|
||||||
|
}
|
||||||
|
|
||||||
// References maps transaction's inputs into a slice of InOuts, effectively
|
// References maps transaction's inputs into a slice of InOuts, effectively
|
||||||
// joining each Input with the corresponding Output.
|
// joining each Input with the corresponding Output.
|
||||||
// @TODO: unfortunately we couldn't attach this method to the Transaction struct in the
|
// @TODO: unfortunately we couldn't attach this method to the Transaction struct in the
|
||||||
|
@ -1070,6 +1168,11 @@ func (bc *Blockchain) FeePerByte(t *transaction.Transaction) util.Fixed8 {
|
||||||
|
|
||||||
// NetworkFee returns network fee.
|
// NetworkFee returns network fee.
|
||||||
func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
|
func (bc *Blockchain) NetworkFee(t *transaction.Transaction) util.Fixed8 {
|
||||||
|
// https://github.com/neo-project/neo/blob/master-2.x/neo/Network/P2P/Payloads/ClaimTransaction.cs#L16
|
||||||
|
if t.Type == transaction.ClaimType || t.Type == transaction.MinerType {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
inputAmount := util.Fixed8FromInt64(0)
|
inputAmount := util.Fixed8FromInt64(0)
|
||||||
refs, err := bc.References(t)
|
refs, err := bc.References(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1175,11 +1278,97 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
|
||||||
if bc.dao.IsDoubleClaim(claim) {
|
if bc.dao.IsDoubleClaim(claim) {
|
||||||
return errors.New("double claim")
|
return errors.New("double claim")
|
||||||
}
|
}
|
||||||
|
if err := bc.verifyClaims(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bc.verifyTxWitnesses(t, block)
|
return bc.verifyTxWitnesses(t, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bc *Blockchain) verifyClaims(tx *transaction.Transaction) (err error) {
|
||||||
|
t := tx.Data.(*transaction.ClaimTX)
|
||||||
|
var result *transaction.Result
|
||||||
|
results := bc.GetTransactionResults(tx)
|
||||||
|
for i := range results {
|
||||||
|
if results[i].AssetID == UtilityTokenID() {
|
||||||
|
result = results[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil || result.Amount.GreaterThan(0) {
|
||||||
|
return errors.New("invalid output in claim tx")
|
||||||
|
}
|
||||||
|
|
||||||
|
bonus, err := bc.calculateBonus(t.Claims)
|
||||||
|
if err == nil && bonus != -result.Amount {
|
||||||
|
return fmt.Errorf("wrong bonus calculated in claim tx: %s != %s",
|
||||||
|
bonus.String(), (-result.Amount).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *Blockchain) calculateBonus(claims []transaction.Input) (util.Fixed8, error) {
|
||||||
|
unclaimed := []*spentCoin{}
|
||||||
|
inputs := transaction.GroupInputsByPrevHash(claims)
|
||||||
|
|
||||||
|
for _, group := range inputs {
|
||||||
|
h := group[0].PrevHash
|
||||||
|
claimable, err := bc.getUnclaimed(h)
|
||||||
|
if err != nil || len(claimable) == 0 {
|
||||||
|
return 0, errors.New("no unclaimed inputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range group {
|
||||||
|
s, ok := claimable[c.PrevIndex]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("can't find spent coins for %s (%d)", c.PrevHash.StringLE(), c.PrevIndex)
|
||||||
|
}
|
||||||
|
unclaimed = append(unclaimed, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bc.calculateBonusInternal(unclaimed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *Blockchain) calculateBonusInternal(scs []*spentCoin) (util.Fixed8, error) {
|
||||||
|
var claimed util.Fixed8
|
||||||
|
for _, sc := range scs {
|
||||||
|
gen, sys, err := bc.CalculateClaimable(sc.Output.Amount, sc.StartHeight, sc.EndHeight)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
claimed += gen + sys
|
||||||
|
}
|
||||||
|
|
||||||
|
return claimed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *Blockchain) getUnclaimed(h util.Uint256) (map[uint16]*spentCoin, error) {
|
||||||
|
tx, txHeight, err := bc.GetTransaction(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scs, err := bc.dao.GetSpentCoinState(h)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[uint16]*spentCoin)
|
||||||
|
for i, height := range scs.items {
|
||||||
|
result[i] = &spentCoin{
|
||||||
|
Output: &tx.Outputs[i],
|
||||||
|
StartHeight: txHeight,
|
||||||
|
EndHeight: height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// isTxStillRelevant is a callback for mempool transaction filtering after the
|
// isTxStillRelevant is a callback for mempool transaction filtering after the
|
||||||
// new block addition. It returns false for transactions already present in the
|
// new block addition. It returns false for transactions already present in the
|
||||||
// chain (added by the new block), transactions using some inputs that are
|
// chain (added by the new block), transactions using some inputs that are
|
||||||
|
@ -1396,7 +1585,7 @@ func (bc *Blockchain) GetValidators(txes ...*transaction.Transaction) ([]*keys.P
|
||||||
for _, tx := range txes {
|
for _, tx := range txes {
|
||||||
// iterate through outputs
|
// iterate through outputs
|
||||||
for index, output := range tx.Outputs {
|
for index, output := range tx.Outputs {
|
||||||
accountState, err := cache.GetAccountState(output.ScriptHash)
|
accountState, err := cache.GetAccountStateOrNew(output.ScriptHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,43 @@ func TestGetTransaction(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetClaimable(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
|
||||||
|
bc.generationAmount = []int{4, 3, 2, 1}
|
||||||
|
bc.decrementInterval = 2
|
||||||
|
_, err := bc.genBlocks(10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("first generation period", func(t *testing.T) {
|
||||||
|
amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 0, 2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 8, amount)
|
||||||
|
require.EqualValues(t, 0, sysfee)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("a number of full periods", func(t *testing.T) {
|
||||||
|
amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 0, 6)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 4+4+3+3+2+2, amount)
|
||||||
|
require.EqualValues(t, 0, sysfee)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("start from the 2-nd block", func(t *testing.T) {
|
||||||
|
amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 1, 7)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 4+3+3+2+2+1, amount)
|
||||||
|
require.EqualValues(t, 0, sysfee)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("end height after generation has ended", func(t *testing.T) {
|
||||||
|
amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 1, 10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 4+3+3+2+2+1+1, amount)
|
||||||
|
require.EqualValues(t, 0, sysfee)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestClose(t *testing.T) {
|
func TestClose(t *testing.T) {
|
||||||
defer func() {
|
defer func() {
|
||||||
r := recover()
|
r := recover()
|
||||||
|
|
|
@ -20,6 +20,7 @@ type Blockchainer interface {
|
||||||
AddHeaders(...*block.Header) error
|
AddHeaders(...*block.Header) error
|
||||||
AddBlock(*block.Block) error
|
AddBlock(*block.Block) error
|
||||||
BlockHeight() uint32
|
BlockHeight() uint32
|
||||||
|
CalculateClaimable(value util.Fixed8, startHeight, endHeight uint32) (util.Fixed8, util.Fixed8, error)
|
||||||
Close()
|
Close()
|
||||||
HeaderHeight() uint32
|
HeaderHeight() uint32
|
||||||
GetBlock(hash util.Uint256) (*block.Block, error)
|
GetBlock(hash util.Uint256) (*block.Block, error)
|
||||||
|
|
|
@ -375,17 +375,18 @@ func makeStorageItemKey(scripthash util.Uint160, key []byte) []byte {
|
||||||
// -- other.
|
// -- other.
|
||||||
|
|
||||||
// GetBlock returns Block by the given hash if it exists in the store.
|
// GetBlock returns Block by the given hash if it exists in the store.
|
||||||
func (dao *dao) GetBlock(hash util.Uint256) (*block.Block, error) {
|
func (dao *dao) GetBlock(hash util.Uint256) (*block.Block, uint32, error) {
|
||||||
key := storage.AppendPrefix(storage.DataBlock, hash.BytesLE())
|
key := storage.AppendPrefix(storage.DataBlock, hash.BytesLE())
|
||||||
b, err := dao.store.Get(key)
|
b, err := dao.store.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
block, err := block.NewBlockFromTrimmedBytes(b)
|
|
||||||
|
block, err := block.NewBlockFromTrimmedBytes(b[4:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
return block, err
|
return block, binary.LittleEndian.Uint32(b[:4]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVersion attempts to get the current version stored in the
|
// GetVersion attempts to get the current version stored in the
|
||||||
|
@ -508,8 +509,7 @@ func (dao *dao) StoreAsBlock(block *block.Block, sysFee uint32) error {
|
||||||
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
|
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
|
||||||
buf = io.NewBufBinWriter()
|
buf = io.NewBufBinWriter()
|
||||||
)
|
)
|
||||||
// sysFee needs to be handled somehow
|
buf.WriteU32LE(sysFee)
|
||||||
// buf.WriteU32LE(sysFee)
|
|
||||||
b, err := block.Trim()
|
b, err := block.Trim()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -254,7 +254,7 @@ func TestDeleteStorageItem(t *testing.T) {
|
||||||
func TestGetBlock_NotExists(t *testing.T) {
|
func TestGetBlock_NotExists(t *testing.T) {
|
||||||
dao := newDao(storage.NewMemoryStore())
|
dao := newDao(storage.NewMemoryStore())
|
||||||
hash := random.Uint256()
|
hash := random.Uint256()
|
||||||
block, err := dao.GetBlock(hash)
|
block, _, err := dao.GetBlock(hash)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Nil(t, block)
|
require.Nil(t, block)
|
||||||
}
|
}
|
||||||
|
@ -270,11 +270,12 @@ func TestPutGetBlock(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
hash := b.Hash()
|
hash := b.Hash()
|
||||||
err := dao.StoreAsBlock(b, 0)
|
err := dao.StoreAsBlock(b, 42)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
gotBlock, err := dao.GetBlock(hash)
|
gotBlock, sysfee, err := dao.GetBlock(hash)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, gotBlock)
|
require.NotNil(t, gotBlock)
|
||||||
|
require.EqualValues(t, 42, sysfee)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetVersion_NoVersion(t *testing.T) {
|
func TestGetVersion_NoVersion(t *testing.T) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/emit"
|
"github.com/CityOfZion/neo-go/pkg/vm/emit"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
@ -158,6 +159,11 @@ func newDumbBlock() *block.Block {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getInvocationScript(data []byte, priv *keys.PrivateKey) []byte {
|
||||||
|
signature := priv.Sign(data)
|
||||||
|
return append([]byte{byte(opcode.PUSHBYTES64)}, signature...)
|
||||||
|
}
|
||||||
|
|
||||||
// This function generates "../rpc/testdata/testblocks.acc" file which contains data
|
// This function generates "../rpc/testdata/testblocks.acc" file which contains data
|
||||||
// for RPC unit tests.
|
// for RPC unit tests.
|
||||||
// To generate new "../rpc/testdata/testblocks.acc", follow the steps:
|
// To generate new "../rpc/testdata/testblocks.acc", follow the steps:
|
||||||
|
@ -165,6 +171,8 @@ func newDumbBlock() *block.Block {
|
||||||
// 2. Add specific test-case into "neo-go/pkg/core/blockchain_test.go"
|
// 2. Add specific test-case into "neo-go/pkg/core/blockchain_test.go"
|
||||||
// 3. Run tests with `$ make test`
|
// 3. Run tests with `$ make test`
|
||||||
func _(t *testing.T) {
|
func _(t *testing.T) {
|
||||||
|
const prefix = "../rpc/server/testdata/"
|
||||||
|
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
n := 50
|
n := 50
|
||||||
_, err := bc.genBlocks(n)
|
_, err := bc.genBlocks(n)
|
||||||
|
@ -172,7 +180,7 @@ func _(t *testing.T) {
|
||||||
|
|
||||||
tx1 := newMinerTX()
|
tx1 := newMinerTX()
|
||||||
|
|
||||||
avm, err := ioutil.ReadFile("../rpc/testdata/test_contract.avm")
|
avm, err := ioutil.ReadFile(prefix + "test_contract.avm")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var props smartcontract.PropertyState
|
var props smartcontract.PropertyState
|
||||||
|
@ -206,10 +214,76 @@ func _(t *testing.T) {
|
||||||
emit.AppCall(script.BinWriter, hash.Hash160(avm), false)
|
emit.AppCall(script.BinWriter, hash.Hash160(avm), false)
|
||||||
|
|
||||||
tx3 := transaction.NewInvocationTX(script.Bytes(), util.Fixed8FromFloat(100))
|
tx3 := transaction.NewInvocationTX(script.Bytes(), util.Fixed8FromFloat(100))
|
||||||
b := bc.newBlock(newMinerTX(), tx3)
|
|
||||||
|
tx4 := transaction.NewContractTX()
|
||||||
|
h, err := util.Uint256DecodeStringBE("6da730b566db183bfceb863b780cd92dee2b497e5a023c322c1eaca81cf9ad7a")
|
||||||
|
require.NoError(t, err)
|
||||||
|
tx4.AddInput(&transaction.Input{
|
||||||
|
PrevHash: h,
|
||||||
|
PrevIndex: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
// multisig address which possess all NEO
|
||||||
|
scriptHash, err := util.Uint160DecodeStringBE("be48d3a3f5d10013ab9ffee489706078714f1ea2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
priv, err := keys.NewPrivateKeyFromWIF(privNetKeys[0])
|
||||||
|
require.NoError(t, err)
|
||||||
|
tx4.AddOutput(&transaction.Output{
|
||||||
|
AssetID: GoverningTokenID(),
|
||||||
|
Amount: util.Fixed8FromInt64(1000),
|
||||||
|
ScriptHash: priv.GetScriptHash(),
|
||||||
|
Position: 0,
|
||||||
|
})
|
||||||
|
tx4.AddOutput(&transaction.Output{
|
||||||
|
AssetID: GoverningTokenID(),
|
||||||
|
Amount: util.Fixed8FromInt64(99999000),
|
||||||
|
ScriptHash: scriptHash,
|
||||||
|
Position: 1,
|
||||||
|
})
|
||||||
|
tx4.Data = new(transaction.ContractTX)
|
||||||
|
|
||||||
|
validators, err := getValidators(bc.config)
|
||||||
|
require.NoError(t, err)
|
||||||
|
rawScript, err := smartcontract.CreateMultiSigRedeemScript(len(bc.config.StandbyValidators)/2+1, validators)
|
||||||
|
require.NoError(t, err)
|
||||||
|
data := tx4.GetSignedPart()
|
||||||
|
|
||||||
|
var invoc []byte
|
||||||
|
for i := range privNetKeys {
|
||||||
|
priv, err := keys.NewPrivateKeyFromWIF(privNetKeys[i])
|
||||||
|
require.NoError(t, err)
|
||||||
|
invoc = append(invoc, getInvocationScript(data, priv)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx4.Scripts = []transaction.Witness{{
|
||||||
|
InvocationScript: invoc,
|
||||||
|
VerificationScript: rawScript,
|
||||||
|
}}
|
||||||
|
|
||||||
|
b := bc.newBlock(newMinerTX(), tx3, tx4)
|
||||||
require.NoError(t, bc.AddBlock(b))
|
require.NoError(t, bc.AddBlock(b))
|
||||||
|
|
||||||
outStream, err := os.Create("../rpc/testdata/testblocks.acc")
|
priv1, err := keys.NewPrivateKeyFromWIF(privNetKeys[1])
|
||||||
|
require.NoError(t, err)
|
||||||
|
tx5 := transaction.NewContractTX()
|
||||||
|
tx5.Data = new(transaction.ContractTX)
|
||||||
|
tx5.AddInput(&transaction.Input{
|
||||||
|
PrevHash: tx4.Hash(),
|
||||||
|
PrevIndex: 0,
|
||||||
|
})
|
||||||
|
tx5.AddOutput(&transaction.Output{
|
||||||
|
AssetID: GoverningTokenID(),
|
||||||
|
Amount: util.Fixed8FromInt64(1000),
|
||||||
|
ScriptHash: priv1.GetScriptHash(),
|
||||||
|
})
|
||||||
|
|
||||||
|
acc, err := wallet.NewAccountFromWIF(priv.WIF())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, acc.SignTx(tx5))
|
||||||
|
b = bc.newBlock(newMinerTX(), tx5)
|
||||||
|
require.NoError(t, bc.AddBlock(b))
|
||||||
|
|
||||||
|
outStream, err := os.Create(prefix + "testblocks.acc")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer outStream.Close()
|
defer outStream.Close()
|
||||||
|
|
||||||
|
@ -225,6 +299,7 @@ func _(t *testing.T) {
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
b.EncodeBinary(buf.BinWriter)
|
b.EncodeBinary(buf.BinWriter)
|
||||||
bytes := buf.Bytes()
|
bytes := buf.Bytes()
|
||||||
|
writer.WriteU32LE(uint32(len(bytes)))
|
||||||
writer.WriteBytes(bytes)
|
writer.WriteBytes(bytes)
|
||||||
require.NoError(t, writer.Err)
|
require.NoError(t, writer.Err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
@ -14,6 +15,13 @@ type SpentCoinState struct {
|
||||||
items map[uint16]uint32
|
items map[uint16]uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// spentCoin represents the state of a single spent coin output.
|
||||||
|
type spentCoin struct {
|
||||||
|
Output *transaction.Output
|
||||||
|
StartHeight uint32
|
||||||
|
EndHeight uint32
|
||||||
|
}
|
||||||
|
|
||||||
// NewSpentCoinState returns a new SpentCoinState object.
|
// NewSpentCoinState returns a new SpentCoinState object.
|
||||||
func NewSpentCoinState(hash util.Uint256, height uint32) *SpentCoinState {
|
func NewSpentCoinState(hash util.Uint256, height uint32) *SpentCoinState {
|
||||||
return &SpentCoinState{
|
return &SpentCoinState{
|
||||||
|
|
|
@ -14,6 +14,16 @@ type UnspentBalance struct {
|
||||||
Value util.Fixed8 `json:"value"`
|
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).
|
// UnspentBalances is a slice of UnspentBalance (mostly needed to sort them).
|
||||||
type UnspentBalances []UnspentBalance
|
type UnspentBalances []UnspentBalance
|
||||||
|
|
||||||
|
@ -24,6 +34,7 @@ type Account struct {
|
||||||
IsFrozen bool
|
IsFrozen bool
|
||||||
Votes []*keys.PublicKey
|
Votes []*keys.PublicKey
|
||||||
Balances map[util.Uint256][]UnspentBalance
|
Balances map[util.Uint256][]UnspentBalance
|
||||||
|
Unclaimed []UnclaimedBalance
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccount returns a new Account object.
|
// NewAccount returns a new Account object.
|
||||||
|
@ -34,6 +45,7 @@ func NewAccount(scriptHash util.Uint160) *Account {
|
||||||
IsFrozen: false,
|
IsFrozen: false,
|
||||||
Votes: []*keys.PublicKey{},
|
Votes: []*keys.PublicKey{},
|
||||||
Balances: make(map[util.Uint256][]UnspentBalance),
|
Balances: make(map[util.Uint256][]UnspentBalance),
|
||||||
|
Unclaimed: []UnclaimedBalance{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +68,8 @@ func (s *Account) DecodeBinary(br *io.BinReader) {
|
||||||
}
|
}
|
||||||
s.Balances[key] = ubs
|
s.Balances[key] = ubs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
br.ReadArray(&s.Unclaimed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary encodes Account to the given BinWriter.
|
// EncodeBinary encodes Account to the given BinWriter.
|
||||||
|
@ -73,6 +87,8 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) {
|
||||||
v[i].EncodeBinary(bw)
|
v[i].EncodeBinary(bw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bw.WriteArray(s.Unclaimed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary implements io.Serializable interface.
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
@ -89,6 +105,24 @@ func (u *UnspentBalance) EncodeBinary(w *io.BinWriter) {
|
||||||
u.Value.EncodeBinary(w)
|
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
|
// GetBalanceValues sums all unspent outputs and returns a map of asset IDs to
|
||||||
// overall balances.
|
// overall balances.
|
||||||
func (s *Account) GetBalanceValues() map[util.Uint256]util.Fixed8 {
|
func (s *Account) GetBalanceValues() map[util.Uint256]util.Fixed8 {
|
||||||
|
|
|
@ -100,6 +100,12 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
|
||||||
br.ReadArray(&t.Attributes)
|
br.ReadArray(&t.Attributes)
|
||||||
br.ReadArray(&t.Inputs)
|
br.ReadArray(&t.Inputs)
|
||||||
br.ReadArray(&t.Outputs)
|
br.ReadArray(&t.Outputs)
|
||||||
|
for i := range t.Outputs {
|
||||||
|
if t.Outputs[i].Amount.LessThan(0) {
|
||||||
|
br.Err = errors.New("negative output")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
br.ReadArray(&t.Scripts)
|
br.ReadArray(&t.Scripts)
|
||||||
|
|
||||||
// Create the hash of the transaction at decode, so we dont need
|
// Create the hash of the transaction at decode, so we dont need
|
||||||
|
@ -197,6 +203,16 @@ func (t Transaction) GroupOutputByAssetID() map[util.Uint256][]*Output {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSignedPart returns a part of the transaction which must be signed.
|
||||||
|
func (t *Transaction) GetSignedPart() []byte {
|
||||||
|
buf := io.NewBufBinWriter()
|
||||||
|
t.encodeHashableFields(buf.BinWriter)
|
||||||
|
if buf.Err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
// Bytes converts the transaction to []byte
|
// Bytes converts the transaction to []byte
|
||||||
func (t *Transaction) Bytes() []byte {
|
func (t *Transaction) Bytes() []byte {
|
||||||
buf := io.NewBufBinWriter()
|
buf := io.NewBufBinWriter()
|
||||||
|
|
|
@ -32,6 +32,9 @@ func (chain testChain) ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithF
|
||||||
func (chain testChain) GetConfig() config.ProtocolConfiguration {
|
func (chain testChain) GetConfig() config.ProtocolConfiguration {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
func (chain testChain) CalculateClaimable(util.Fixed8, uint32, uint32) (util.Fixed8, util.Fixed8, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
|
||||||
func (chain testChain) References(t *transaction.Transaction) ([]transaction.InOut, error) {
|
func (chain testChain) References(t *transaction.Transaction) ([]transaction.InOut, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
|
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/wallet"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +44,16 @@ func (c *Client) GetAccountState(address string) (*result.AccountState, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClaimable returns tx outputs which can be claimed.
|
||||||
|
func (c *Client) GetClaimable(address string) (*result.ClaimableInfo, error) {
|
||||||
|
params := request.NewRawParams(address)
|
||||||
|
resp := new(result.ClaimableInfo)
|
||||||
|
if err := c.performRequest("getclaimable", params, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetUnspents returns UTXOs for the given NEO account.
|
// GetUnspents returns UTXOs for the given NEO account.
|
||||||
func (c *Client) GetUnspents(address string) (*result.Unspents, error) {
|
func (c *Client) GetUnspents(address string) (*result.Unspents, error) {
|
||||||
var (
|
var (
|
||||||
|
@ -108,11 +119,11 @@ func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*respo
|
||||||
// return resp, nil
|
// return resp, nil
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// sendRawTransaction broadcasts a transaction over the NEO network.
|
// SendRawTransaction broadcasts a transaction over the NEO network.
|
||||||
// The given hex string needs to be signed with a keypair.
|
// The given hex string needs to be signed with a keypair.
|
||||||
// When the result of the response object is true, the TX has successfully
|
// When the result of the response object is true, the TX has successfully
|
||||||
// been broadcasted to the network.
|
// been broadcasted to the network.
|
||||||
func (c *Client) sendRawTransaction(rawTX *transaction.Transaction) error {
|
func (c *Client) SendRawTransaction(rawTX *transaction.Transaction) error {
|
||||||
var (
|
var (
|
||||||
params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes()))
|
params = request.NewRawParams(hex.EncodeToString(rawTX.Bytes()))
|
||||||
resp bool
|
resp bool
|
||||||
|
@ -146,7 +157,7 @@ func (c *Client) TransferAsset(asset util.Uint256, address string, amount util.F
|
||||||
if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil {
|
if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil {
|
||||||
return resp, errors.Wrap(err, "failed to create raw transaction")
|
return resp, errors.Wrap(err, "failed to create raw transaction")
|
||||||
}
|
}
|
||||||
if err = c.sendRawTransaction(rawTx); err != nil {
|
if err = c.SendRawTransaction(rawTx); err != nil {
|
||||||
return resp, errors.Wrap(err, "failed to send raw transaction")
|
return resp, errors.Wrap(err, "failed to send raw transaction")
|
||||||
}
|
}
|
||||||
return rawTx.Hash(), nil
|
return rawTx.Hash(), nil
|
||||||
|
@ -169,11 +180,14 @@ func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = request.SignTx(tx, wif); err != nil {
|
acc, err := wallet.NewAccountFromWIF(wif.S)
|
||||||
|
if err != nil {
|
||||||
|
return txHash, err
|
||||||
|
} else if err = acc.SignTx(tx); err != nil {
|
||||||
return txHash, errors.Wrap(err, "failed to sign tx")
|
return txHash, errors.Wrap(err, "failed to sign tx")
|
||||||
}
|
}
|
||||||
txHash = tx.Hash()
|
txHash = tx.Hash()
|
||||||
err = c.sendRawTransaction(tx)
|
err = c.SendRawTransaction(tx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return txHash, errors.Wrap(err, "failed sendning tx")
|
return txHash, errors.Wrap(err, "failed sendning tx")
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/emit"
|
"github.com/CityOfZion/neo-go/pkg/vm/emit"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/wallet"
|
||||||
errs "github.com/pkg/errors"
|
errs "github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,7 +49,9 @@ func CreateRawContractTransaction(params ContractTxParams) (*transaction.Transac
|
||||||
}
|
}
|
||||||
receiverOutput = transaction.NewOutput(assetID, amount, toAddressHash)
|
receiverOutput = transaction.NewOutput(assetID, amount, toAddressHash)
|
||||||
tx.AddOutput(receiverOutput)
|
tx.AddOutput(receiverOutput)
|
||||||
if err = SignTx(tx, &wif); err != nil {
|
if acc, err := wallet.NewAccountFromWIF(wif.S); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if err = acc.SignTx(tx); err != nil {
|
||||||
return nil, errs.Wrap(err, "failed to sign tx")
|
return nil, errs.Wrap(err, "failed to sign tx")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,36 +80,6 @@ func AddInputsAndUnspentsToTx(tx *transaction.Transaction, addr string, assetID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignTx signs given transaction in-place using given key.
|
|
||||||
func SignTx(tx *transaction.Transaction, wif *keys.WIF) error {
|
|
||||||
var witness transaction.Witness
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if witness.InvocationScript, err = GetInvocationScript(tx, wif); err != nil {
|
|
||||||
return errs.Wrap(err, "failed to create invocation script")
|
|
||||||
}
|
|
||||||
witness.VerificationScript = wif.PrivateKey.PublicKey().GetVerificationScript()
|
|
||||||
tx.Scripts = append(tx.Scripts, witness)
|
|
||||||
tx.Hash()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInvocationScript returns NEO VM script containing transaction signature.
|
|
||||||
func GetInvocationScript(tx *transaction.Transaction, wif *keys.WIF) ([]byte, error) {
|
|
||||||
var (
|
|
||||||
buf = io.NewBufBinWriter()
|
|
||||||
signature []byte
|
|
||||||
)
|
|
||||||
tx.EncodeBinary(buf.BinWriter)
|
|
||||||
if buf.Err != nil {
|
|
||||||
return nil, errs.Wrap(buf.Err, "Failed to encode transaction to binary")
|
|
||||||
}
|
|
||||||
data := buf.Bytes()
|
|
||||||
signature = wif.PrivateKey.Sign(data[:(len(data) - 1)])
|
|
||||||
return append([]byte{byte(opcode.PUSHBYTES64)}, signature...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDeploymentScript returns a script that deploys given smart contract
|
// CreateDeploymentScript returns a script that deploys given smart contract
|
||||||
// with its metadata.
|
// with its metadata.
|
||||||
func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, error) {
|
func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, error) {
|
||||||
|
|
22
pkg/rpc/response/result/claimable.go
Normal file
22
pkg/rpc/response/result/claimable.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package result
|
||||||
|
|
||||||
|
import "github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
|
||||||
|
// ClaimableInfo is a result of a getclaimable RPC call.
|
||||||
|
type ClaimableInfo struct {
|
||||||
|
Spents []Claimable `json:"claimable"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Unclaimed util.Fixed8 `json:"unclaimed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claimable represents spent outputs which can be claimed.
|
||||||
|
type Claimable struct {
|
||||||
|
Tx util.Uint256 `json:"txid"`
|
||||||
|
N int `json:"n"`
|
||||||
|
Value util.Fixed8 `json:"value"`
|
||||||
|
StartHeight uint32 `json:"start_height"`
|
||||||
|
EndHeight uint32 `json:"end_height"`
|
||||||
|
Generated util.Fixed8 `json:"generated"`
|
||||||
|
SysFee util.Fixed8 `json:"sys_fee"`
|
||||||
|
Unclaimed util.Fixed8 `json:"unclaimed"`
|
||||||
|
}
|
|
@ -51,6 +51,14 @@ var (
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
getclaimableCalled = prometheus.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Help: "Number of calls to getclaimable rpc endpoint",
|
||||||
|
Name: "getclaimable_called",
|
||||||
|
Namespace: "neogo",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
getconnectioncountCalled = prometheus.NewCounter(
|
getconnectioncountCalled = prometheus.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Help: "Number of calls to getconnectioncount rpc endpoint",
|
Help: "Number of calls to getconnectioncount rpc endpoint",
|
||||||
|
|
|
@ -190,6 +190,10 @@ Methods:
|
||||||
getblocksysfeeCalled.Inc()
|
getblocksysfeeCalled.Inc()
|
||||||
results, resultsErr = s.getBlockSysFee(reqParams)
|
results, resultsErr = s.getBlockSysFee(reqParams)
|
||||||
|
|
||||||
|
case "getclaimable":
|
||||||
|
getclaimableCalled.Inc()
|
||||||
|
results, resultsErr = s.getClaimable(reqParams)
|
||||||
|
|
||||||
case "getconnectioncount":
|
case "getconnectioncount":
|
||||||
getconnectioncountCalled.Inc()
|
getconnectioncountCalled.Inc()
|
||||||
results = s.coreServer.PeerCount()
|
results = s.coreServer.PeerCount()
|
||||||
|
@ -322,6 +326,52 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, error
|
||||||
return result.NewApplicationLog(appExecResult, scriptHash), nil
|
return result.NewApplicationLog(appExecResult, scriptHash), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) getClaimable(ps request.Params) (interface{}, error) {
|
||||||
|
p, ok := ps.ValueWithType(0, request.StringT)
|
||||||
|
if !ok {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
u, err := p.GetUint160FromAddress()
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
|
||||||
|
var unclaimed []state.UnclaimedBalance
|
||||||
|
if acc := s.chain.GetAccountState(u); acc != nil {
|
||||||
|
unclaimed = acc.Unclaimed
|
||||||
|
}
|
||||||
|
|
||||||
|
var sum util.Fixed8
|
||||||
|
claimable := make([]result.Claimable, 0, len(unclaimed))
|
||||||
|
for _, ub := range unclaimed {
|
||||||
|
gen, sys, err := s.chain.CalculateClaimable(ub.Value, ub.Start, ub.End)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Info("error while calculating claim bonus", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
uc := gen.Add(sys)
|
||||||
|
sum += uc
|
||||||
|
|
||||||
|
claimable = append(claimable, result.Claimable{
|
||||||
|
Tx: ub.Tx,
|
||||||
|
N: int(ub.Index),
|
||||||
|
Value: ub.Value,
|
||||||
|
StartHeight: ub.Start,
|
||||||
|
EndHeight: ub.End,
|
||||||
|
Generated: gen,
|
||||||
|
SysFee: sys,
|
||||||
|
Unclaimed: uc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ClaimableInfo{
|
||||||
|
Spents: claimable,
|
||||||
|
Address: p.String(),
|
||||||
|
Unclaimed: sum,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
||||||
param, ok := ps.Value(0)
|
param, ok := ps.Value(0)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -52,6 +52,7 @@ func initServerWithInMemoryChain(t *testing.T) (*core.Blockchain, http.HandlerFu
|
||||||
nBlocks = br.ReadU32LE()
|
nBlocks = br.ReadU32LE()
|
||||||
require.Nil(t, br.Err)
|
require.Nil(t, br.Err)
|
||||||
for i := 0; i < int(nBlocks); i++ {
|
for i := 0; i < int(nBlocks); i++ {
|
||||||
|
_ = br.ReadU32LE()
|
||||||
b := &block.Block{}
|
b := &block.Block{}
|
||||||
b.DecodeBinary(br)
|
b.DecodeBinary(br)
|
||||||
require.Nil(t, br.Err)
|
require.Nil(t, br.Err)
|
||||||
|
|
|
@ -363,6 +363,40 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
fail: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"getclaimable": {
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: "[]",
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid address",
|
||||||
|
params: `["invalid"]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "normal address",
|
||||||
|
params: `["AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU"]`,
|
||||||
|
result: func(*executor) interface{} {
|
||||||
|
// hash of the issueTx
|
||||||
|
h, _ := util.Uint256DecodeStringBE("6da730b566db183bfceb863b780cd92dee2b497e5a023c322c1eaca81cf9ad7a")
|
||||||
|
amount := util.Fixed8FromInt64(52 * 8) // (endHeight - startHeight) * genAmount[0]
|
||||||
|
return &result.ClaimableInfo{
|
||||||
|
Spents: []result.Claimable{
|
||||||
|
{
|
||||||
|
Tx: h,
|
||||||
|
Value: util.Fixed8FromInt64(100000000),
|
||||||
|
EndHeight: 52,
|
||||||
|
Generated: amount,
|
||||||
|
Unclaimed: amount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Address: "AZ81H31DMWzbSnFDLFkzh9vHwaDLayV7fU",
|
||||||
|
Unclaimed: amount,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"getconnectioncount": {
|
"getconnectioncount": {
|
||||||
{
|
{
|
||||||
params: "[]",
|
params: "[]",
|
||||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -122,7 +122,7 @@ func (pt *ParamType) DecodeBinary(r *io.BinReader) {
|
||||||
// bytes, bytearray -> ByteArrayType
|
// bytes, bytearray -> ByteArrayType
|
||||||
// key, publickey -> PublicKeyType
|
// key, publickey -> PublicKeyType
|
||||||
// string -> StringType
|
// string -> StringType
|
||||||
// array -> ArrayType
|
// array, struct -> ArrayType
|
||||||
// map -> MapType
|
// map -> MapType
|
||||||
// interopinterface -> InteropInterfaceType
|
// interopinterface -> InteropInterfaceType
|
||||||
// void -> VoidType
|
// void -> VoidType
|
||||||
|
@ -145,7 +145,7 @@ func ParseParamType(typ string) (ParamType, error) {
|
||||||
return PublicKeyType, nil
|
return PublicKeyType, nil
|
||||||
case "string":
|
case "string":
|
||||||
return StringType, nil
|
return StringType, nil
|
||||||
case "array":
|
case "array", "struct":
|
||||||
return ArrayType, nil
|
return ArrayType, nil
|
||||||
case "map":
|
case "map":
|
||||||
return MapType, nil
|
return MapType, nil
|
||||||
|
|
|
@ -214,7 +214,7 @@ func (i *BoolItem) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *BoolItem) String() string {
|
func (i *BoolItem) String() string {
|
||||||
return "Bool"
|
return "Boolean"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dup implements StackItem interface.
|
// Dup implements StackItem interface.
|
||||||
|
|
|
@ -7,11 +7,13 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Account represents a NEO account. It holds the private and public key
|
// Account represents a NEO account. It holds the private and public key
|
||||||
|
@ -122,6 +124,29 @@ func NewAccount() (*Account, error) {
|
||||||
return newAccountFromPrivateKey(priv), nil
|
return newAccountFromPrivateKey(priv), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignTx signs transaction t and updates it's Witnesses.
|
||||||
|
func (a *Account) SignTx(t *transaction.Transaction) error {
|
||||||
|
if a.privateKey == nil {
|
||||||
|
return errors.New("account is not unlocked")
|
||||||
|
}
|
||||||
|
data := t.GetSignedPart()
|
||||||
|
sign := a.privateKey.Sign(data)
|
||||||
|
|
||||||
|
t.Scripts = append(t.Scripts, transaction.Witness{
|
||||||
|
InvocationScript: append([]byte{byte(opcode.PUSHBYTES64)}, sign...),
|
||||||
|
VerificationScript: a.getVerificationScript(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) getVerificationScript() []byte {
|
||||||
|
if a.Contract != nil {
|
||||||
|
return a.Contract.Script
|
||||||
|
}
|
||||||
|
return a.PrivateKey().PublicKey().GetVerificationScript()
|
||||||
|
}
|
||||||
|
|
||||||
// Decrypt decrypts the EncryptedWIF with the given passphrase returning error
|
// Decrypt decrypts the EncryptedWIF with the given passphrase returning error
|
||||||
// if anything goes wrong.
|
// if anything goes wrong.
|
||||||
func (a *Account) Decrypt(passphrase string) error {
|
func (a *Account) Decrypt(passphrase string) error {
|
||||||
|
|
Loading…
Reference in a new issue