Merge pull request #1016 from nspcc-dev/drop-utxo

Drop UTXO
This commit is contained in:
Roman Khimov 2020-06-06 22:54:57 +03:00 committed by GitHub
commit cd307c3cd0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
113 changed files with 645 additions and 5682 deletions

Binary file not shown.

View file

@ -81,7 +81,6 @@ services:
- ../config/protocol.privnet.docker.single.yml:/config/protocol.privnet.yml
- ./wallets/wallet1.json:/wallet1.json
- volume_chain:/chains
- ./1600-privnet-blocks-single.acc.gz:/privnet-blocks.acc.gz
networks:
neo_go_network:
ipv4_address: 172.200.0.1

View file

@ -30,8 +30,6 @@ LABEL version=$VERSION
WORKDIR /
COPY --from=builder /neo-go/config /config
COPY --from=builder /neo-go/.docker/6000-privnet-blocks.acc.gz /6000-privnet-blocks.acc.gz
COPY --from=builder /neo-go/.docker/1600-privnet-blocks-single.acc.gz /1600-privnet-blocks-single.acc.gz
COPY --from=builder /neo-go/.docker/privnet-entrypoint.sh /usr/bin/privnet-entrypoint.sh
COPY --from=builder /go/bin/neo-go /usr/bin/neo-go
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

View file

@ -121,15 +121,4 @@ func writeParameterContext(c *context.ParameterContext, filename string) error {
func printTxInfo(t *transaction.Transaction) {
fmt.Printf("Hash: %s\n", t.Hash().StringLE())
for i := range t.Inputs {
fmt.Printf("Input%02d: [%2d] %s\n", i, t.Inputs[i].PrevIndex, t.Inputs[i].PrevHash.StringLE())
}
for i := range t.Outputs {
fmt.Printf("Output%02d:\n", i)
fmt.Printf("\tAssetID : %s\n", t.Outputs[i].AssetID.StringLE())
fmt.Printf("\tAmount : %s\n", t.Outputs[i].Amount.String())
h := t.Outputs[i].ScriptHash
fmt.Printf("\tScriptHash: %s\n", t.Outputs[i].ScriptHash.StringLE())
fmt.Printf("\tToAddr : %s\n", address.Uint160ToString(h))
}
}

View file

@ -1,12 +1,15 @@
package wallet
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli"
@ -82,6 +85,7 @@ func newNEP5Commands() []cli.Command {
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
outFlag,
timeoutFlag,
fromAddrFlag,
toAddrFlag,
@ -341,11 +345,30 @@ func transferNEP5(ctx *cli.Context) error {
return cli.NewExitError(err, 1)
}
hash, err := c.TransferNEP5(acc, to, token, amount, gas)
tx, err := c.CreateNEP5TransferTx(acc, to, token.Hash, amount, gas)
if err != nil {
return cli.NewExitError(err, 1)
}
fmt.Println(hash.StringLE())
if outFile := ctx.String("out"); outFile != "" {
priv := acc.PrivateKey()
pub := priv.PublicKey()
sign := priv.Sign(tx.GetSignedPart())
scCtx := context.NewParameterContext("Neo.Core.ContractTransaction", tx)
if err := scCtx.AddSignature(acc.Contract, pub, sign); err != nil {
return cli.NewExitError(fmt.Errorf("can't add signature: %v", err), 1)
} else if data, err := json.Marshal(scCtx); err != nil {
return cli.NewExitError(fmt.Errorf("can't marshal tx to JSON: %v", err), 1)
} else if err := ioutil.WriteFile(outFile, data, 0644); err != nil {
return cli.NewExitError(fmt.Errorf("can't write tx to file: %v", err), 1)
}
} else {
_ = acc.SignTx(tx)
if err := c.SendRawTransaction(tx); err != nil {
return cli.NewExitError(err, 1)
}
}
fmt.Println(tx.Hash().StringLE())
return nil
}

View file

@ -4,22 +4,16 @@ import (
"bufio"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"syscall"
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
context2 "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli"
@ -179,29 +173,6 @@ func NewCommands() []cli.Command {
forceFlag,
},
},
{
Name: "transfer",
Usage: "transfer NEO/GAS",
UsageText: "transfer --path <path> --from <addr> --to <addr>" +
" --amount <amount> --asset [NEO|GAS|<hex-id>] [--out <path>]",
Action: transferAsset,
Flags: []cli.Flag{
walletPathFlag,
rpcFlag,
timeoutFlag,
outFlag,
fromAddrFlag,
toAddrFlag,
cli.StringFlag{
Name: "amount",
Usage: "Amount of asset to send",
},
cli.StringFlag{
Name: "asset",
Usage: "Asset ID",
},
},
},
{
Name: "multisig",
Usage: "work with multisig address",
@ -247,46 +218,18 @@ func claimGas(ctx *cli.Context) error {
if err != nil {
return cli.NewExitError(err, 1)
}
info, err := c.GetClaimable(addrFlag.String())
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.NewClaimTX(&claim)
validUntilBlock, err := c.CalculateValidUntilBlock()
// Temporary.
neoHash, err := util.Uint160DecodeStringLE("3b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c")
if err != nil {
return cli.NewExitError(err, 1)
}
tx.ValidUntilBlock = validUntilBlock
tx.Sender = scriptHash
tx.AddOutput(&transaction.Output{
AssetID: core.UtilityTokenID(),
Amount: info.Unclaimed,
ScriptHash: scriptHash,
})
err = c.AddNetworkFee(tx, acc)
hash, err := c.TransferNEP5(acc, scriptHash, neoHash, 0, 0)
if err != nil {
return cli.NewExitError(err, 1)
}
_ = acc.SignTx(tx)
if err := c.SendRawTransaction(tx); err != nil {
return cli.NewExitError(err, 1)
}
fmt.Println(tx.Hash().StringLE())
fmt.Println(hash.StringLE())
return nil
}
@ -477,99 +420,6 @@ func askForConsent() bool {
return false
}
func transferAsset(ctx *cli.Context) error {
wall, err := openWallet(ctx.String("path"))
if err != nil {
return cli.NewExitError(err, 1)
}
defer wall.Close()
fromFlag := ctx.Generic("from").(*flags.Address)
if !fromFlag.IsSet {
return cli.NewExitError("'from' address was not provided", 1)
}
from := fromFlag.Uint160()
acc := wall.GetAccount(from)
if acc == nil {
return cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", from), 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()
validUntilBlock, err := c.CalculateValidUntilBlock()
if err != nil {
return cli.NewExitError(err, 1)
}
tx.ValidUntilBlock = validUntilBlock
if err := request.AddInputsAndUnspentsToTx(tx, fromFlag.String(), asset, amount, c); err != nil {
return cli.NewExitError(err, 1)
}
tx.Sender = from
toFlag := ctx.Generic("to").(*flags.Address)
if !toFlag.IsSet {
return cli.NewExitError("'to' address was not provided", 1)
}
toAddr := toFlag.Uint160()
tx.AddOutput(&transaction.Output{
AssetID: asset,
Amount: amount,
ScriptHash: toAddr,
Position: 1,
})
err = c.AddNetworkFee(tx, acc)
if err != nil {
return cli.NewExitError(err, 1)
}
if outFile := ctx.String("out"); outFile != "" {
priv := acc.PrivateKey()
pub := priv.PublicKey()
sign := priv.Sign(tx.GetSignedPart())
c := context2.NewParameterContext("Neo.Core.ContractTransaction", tx)
if err := c.AddSignature(acc.Contract, pub, sign); err != nil {
return cli.NewExitError(fmt.Errorf("can't add signature: %v", err), 1)
} else if data, err := json.Marshal(c); err != nil {
return cli.NewExitError(fmt.Errorf("can't marshal tx to JSON: %v", err), 1)
} else if err := ioutil.WriteFile(outFile, data, 0644); err != nil {
return cli.NewExitError(fmt.Errorf("can't write tx to file: %v", err), 1)
}
} else {
_ = 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)
@ -659,18 +509,6 @@ func openWallet(path string) (*wallet.Wallet, error) {
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) {
// note: NEP2 strings always have length of 58 even though
// base58 strings can have different lengths even if slice lengths are equal

View file

@ -18,9 +18,6 @@ ProtocolConfiguration:
- seed3.neo.org:10333
- seed4.neo.org:10333
- seed5.neo.org:10333
SystemFee:
IssueTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: false
FreeGasLimit: 10.0

View file

@ -14,9 +14,6 @@ ProtocolConfiguration:
- 172.200.0.2:20334
- 172.200.0.3:20335
- 172.200.0.4:20336
SystemFee:
IssueTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true

View file

@ -14,9 +14,6 @@ ProtocolConfiguration:
- 172.200.0.2:20334
- 172.200.0.3:20335
- 172.200.0.4:20336
SystemFee:
IssueTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true

View file

@ -8,9 +8,6 @@ ProtocolConfiguration:
- 02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2
SeedList:
- 172.200.0.1:20333
SystemFee:
IssueTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true

View file

@ -14,9 +14,6 @@ ProtocolConfiguration:
- 172.200.0.2:20334
- 172.200.0.3:20335
- 172.200.0.4:20336
SystemFee:
IssueTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true

View file

@ -14,9 +14,6 @@ ProtocolConfiguration:
- 172.200.0.2:20334
- 172.200.0.3:20335
- 172.200.0.4:20336
SystemFee:
IssueTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true

View file

@ -14,10 +14,6 @@ ProtocolConfiguration:
- 127.0.0.1:20334
- 127.0.0.1:20335
- 127.0.0.1:20336
SystemFee:
EnrollmentTransaction: 1000
IssueTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true

View file

@ -18,9 +18,6 @@ ProtocolConfiguration:
- seed3t.neo.org:20333
- seed4t.neo.org:20333
- seed5t.neo.org:20333
SystemFee:
IssueTransaction: 5
RegisterTransaction: 100
VerifyBlocks: true
VerifyTransactions: false
FreeGasLimit: 10.0

View file

@ -13,9 +13,6 @@ ProtocolConfiguration:
- 127.0.0.1:20334
- 127.0.0.1:20335
- 127.0.0.1:20336
SystemFee:
IssueTransaction: 500
RegisterTransaction: 10000
VerifyBlocks: true
VerifyTransactions: true

View file

@ -59,10 +59,6 @@ ProtocolConfiguration:
- 127.0.0.1:20334
- 127.0.0.1:20335
- 127.0.0.1:20336
SystemFee:
EnrollmentTransaction: 1000
IssueTransaction: 500
RegisterTransaction: 10000
ApplicationConfiguration:
DataDirectoryPath: "./chains/privnet"

View file

@ -34,16 +34,13 @@ which would yield the response:
| Method |
| ------- |
| `getaccountstate` |
| `getapplicationlog` |
| `getassetstate` |
| `getbestblockhash` |
| `getblock` |
| `getblockcount` |
| `getblockhash` |
| `getblockheader` |
| `getblocksysfee` |
| `getclaimable` |
| `getconnectioncount` |
| `getcontractstate` |
| `getnep5balances` |
@ -53,9 +50,7 @@ which would yield the response:
| `getrawtransaction` |
| `getstorage` |
| `gettransactionheight` |
| `gettxout` |
| `getunclaimed` |
| `getunspents` |
| `getunclaimedgas` |
| `getvalidators` |
| `getversion` |
| `invoke` |
@ -77,6 +72,11 @@ in returning it.
Both methods also don't currently support arrays in function parameters.
##### `getunclaimedgas`
It's possible to call this method for any address with neo-go, unlike with C#
node where it only works for addresses from opened wallet.
### Unsupported methods
Methods listed down below are not going to be supported for various reasons
@ -86,10 +86,9 @@ and we're not accepting issues related to them.
| ------- | ------------|
| `claimgas` | Doesn't fit neo-go wallet model, use CLI to do that |
| `dumpprivkey` | Shouldn't exist for security reasons, see `claimgas` comment also |
| `getbalance` | Use `getaccountstate` instead, see `claimgas` comment also |
| `getbalance` | To be implemented |
| `getmetricblocktimestamp` | Not really useful, use other means for node monitoring |
| `getnewaddress` | See `claimgas` comment |
| `getunclaimedgas` | Use `getunclaimed` instead, see `claimgas` comment also |
| `getwalletheight` | Not applicable to neo-go, see `claimgas` comment |
| `importprivkey` | Not applicable to neo-go, see `claimgas` comment |
| `listaddress` | Not applicable to neo-go, see `claimgas` comment |

View file

@ -77,7 +77,7 @@ func getTX(t *testing.B, wif *keys.WIF) *transaction.Transaction {
fromAddressHash, err := address.StringToUint160(fromAddress)
require.NoError(t, err)
tx := transaction.NewInvocationTX([]byte{0x51}, 1)
tx := transaction.New([]byte{0x51}, 1)
tx.Version = 0
tx.Sender = fromAddressHash
tx.Attributes = append(tx.Attributes,

View file

@ -40,7 +40,6 @@ var syscalls = map[string]map[string]string{
},
"blockchain": {
"GetAccount": "Neo.Blockchain.GetAccount",
"GetAsset": "Neo.Blockchain.GetAsset",
"GetBlock": "Neo.Blockchain.GetBlock",
"GetContract": "Neo.Blockchain.GetContract",
"GetHeader": "Neo.Blockchain.GetHeader",
@ -65,27 +64,9 @@ var syscalls = map[string]map[string]string{
"GetTransaction": "Neo.Block.GetTransaction",
},
"transaction": {
"GetAttributes": "Neo.Transaction.GetAttributes",
"GetHash": "Neo.Transaction.GetHash",
"GetInputs": "Neo.Transaction.GetInputs",
"GetOutputs": "Neo.Transaction.GetOutputs",
"GetReferences": "Neo.Transaction.GetReferences",
"GetScript": "Neo.InvocationTransaction.GetScript",
"GetType": "Neo.Transaction.GetType",
"GetUnspentCoins": "Neo.Transaction.GetUnspentCoins",
"GetWitnesses": "Neo.Transaction.GetWitnesses",
},
"asset": {
"Create": "Neo.Asset.Create",
"GetAdmin": "Neo.Asset.GetAdmin",
"GetAmount": "Neo.Asset.GetAmount",
"GetAssetID": "Neo.Asset.GetAssetID",
"GetAssetType": "Neo.Asset.GetAssetType",
"GetAvailable": "Neo.Asset.GetAvailable",
"GetIssuer": "Neo.Asset.GetIssuer",
"GetOwner": "Neo.Asset.GetOwner",
"GetPrecision": "Neo.Asset.GetPrecision",
"Renew": "Neo.Asset.Renew",
"GetAttributes": "Neo.Transaction.GetAttributes",
"GetHash": "Neo.Transaction.GetHash",
"GetWitnesses": "Neo.Transaction.GetWitnesses",
},
"contract": {
"GetScript": "Neo.Contract.GetScript",
@ -95,15 +76,6 @@ var syscalls = map[string]map[string]string{
"Migrate": "Neo.Contract.Migrate",
"GetStorageContext": "Neo.Contract.GetStorageContext",
},
"input": {
"GetHash": "Neo.Input.GetHash",
"GetIndex": "Neo.Input.GetIndex",
},
"output": {
"GetAssetID": "Neo.Output.GetAssetID",
"GetValue": "Neo.Output.GetValue",
"GetScriptHash": "Neo.Output.GetScriptHash",
},
"engine": {
"GetScriptContainer": "System.ExecutionEngine.GetScriptContainer",
"GetCallingScriptHash": "System.ExecutionEngine.GetCallingScriptHash",

View file

@ -40,9 +40,6 @@ func Load(path string, netMode NetMode) (Config, error) {
}
config := Config{
ProtocolConfiguration: ProtocolConfiguration{
SystemFee: SystemFee{},
},
ApplicationConfiguration: ApplicationConfiguration{
PingInterval: 30,
PingTimeout: 90,

View file

@ -1,7 +1,6 @@
package config
import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/util"
)
@ -34,23 +33,16 @@ type (
MaxFreeTransactionsPerBlock int `yaml:"MaxFreeTransactionsPerBlock"`
MemPoolSize int `yaml:"MemPoolSize"`
// SaveStorageBatch enables storage batch saving before every persist.
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
SecondsPerBlock int `yaml:"SecondsPerBlock"`
SeedList []string `yaml:"SeedList"`
StandbyValidators []string `yaml:"StandbyValidators"`
SystemFee SystemFee `yaml:"SystemFee"`
SaveStorageBatch bool `yaml:"SaveStorageBatch"`
SecondsPerBlock int `yaml:"SecondsPerBlock"`
SeedList []string `yaml:"SeedList"`
StandbyValidators []string `yaml:"StandbyValidators"`
// Whether to verify received blocks.
VerifyBlocks bool `yaml:"VerifyBlocks"`
// Whether to verify transactions in received blocks.
VerifyTransactions bool `yaml:"VerifyTransactions"`
}
// SystemFee fees related to system.
SystemFee struct {
IssueTransaction int64 `yaml:"IssueTransaction"`
RegisterTransaction int64 `yaml:"RegisterTransaction"`
}
// NetMode describes the mode the blockchain will operate on.
NetMode uint32
)
@ -70,15 +62,3 @@ func (n NetMode) String() string {
return "net unknown"
}
}
// TryGetValue returns the system fee base on transaction type.
func (s SystemFee) TryGetValue(txType transaction.TXType) util.Fixed8 {
switch txType {
case transaction.IssueType:
return util.Fixed8FromInt64(s.IssueTransaction)
case transaction.RegisterType:
return util.Fixed8FromInt64(s.RegisterTransaction)
default:
return util.Fixed8FromInt64(0)
}
}

View file

@ -8,6 +8,7 @@ import (
"github.com/nspcc-dev/dbft/crypto"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require"
)
@ -44,7 +45,7 @@ func TestNeoBlock_Setters(t *testing.T) {
b.Block.PrevHash = util.Uint256{9, 8, 7}
require.Equal(t, util.Uint256{9, 8, 7}, b.PrevHash())
txx := []block.Transaction{transaction.NewIssueTX()}
txx := []block.Transaction{transaction.New([]byte{byte(opcode.PUSH1)}, 1)}
b.SetTransactions(txx)
require.Equal(t, txx, b.Transactions())
}

View file

@ -12,7 +12,6 @@ import (
"github.com/nspcc-dev/dbft/payload"
coreb "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/mempool"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
@ -394,13 +393,13 @@ func (s *service) getBlock(h util.Uint256) block.Block {
func (s *service) getVerifiedTx(count int) []block.Transaction {
pool := s.Config.Chain.GetMemPool()
var txx []mempool.TxWithFee
var txx []*transaction.Transaction
if s.dbft.ViewNumber > 0 {
txx = make([]mempool.TxWithFee, 0, len(s.lastProposal))
txx = make([]*transaction.Transaction, 0, len(s.lastProposal))
for i := range s.lastProposal {
if tx, fee, ok := pool.TryGetValue(s.lastProposal[i]); ok {
txx = append(txx, mempool.TxWithFee{Tx: tx, Fee: fee})
if tx, ok := pool.TryGetValue(s.lastProposal[i]); ok {
txx = append(txx, tx)
}
}
@ -417,7 +416,7 @@ func (s *service) getVerifiedTx(count int) []block.Transaction {
res := make([]block.Transaction, len(txx))
for i := range txx {
res[i] = txx[i].Tx
res[i] = txx[i]
}
return res

View file

@ -15,6 +15,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
@ -22,7 +23,7 @@ import (
func TestNewService(t *testing.T) {
srv := newTestService(t)
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.ValidUntilBlock = 1
addSender(t, tx)
signTx(t, srv.Chain.FeePerByte(), tx)
@ -39,7 +40,7 @@ func TestService_GetVerified(t *testing.T) {
srv := newTestService(t)
var txs []*transaction.Transaction
for i := 0; i < 4; i++ {
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = 123 + uint32(i)
tx.ValidUntilBlock = 1
txs = append(txs, tx)
@ -53,7 +54,7 @@ func TestService_GetVerified(t *testing.T) {
p := new(Payload)
p.message = &message{}
p.SetType(payload.PrepareRequestType)
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = 999
p.SetPayload(&prepareRequest{transactionHashes: hashes})
p.SetValidatorIndex(1)
@ -120,7 +121,7 @@ func TestService_getTx(t *testing.T) {
srv := newTestService(t)
t.Run("transaction in mempool", func(t *testing.T) {
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = 1234
tx.ValidUntilBlock = 1
addSender(t, tx)
@ -137,7 +138,7 @@ func TestService_getTx(t *testing.T) {
})
t.Run("transaction in local cache", func(t *testing.T) {
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = 4321
tx.ValidUntilBlock = 1
h := tx.Hash()

View file

@ -160,6 +160,9 @@ func (b *Block) DecodeBinary(br *io.BinReader) {
txes[i] = tx
}
b.Transactions = txes
if br.Err != nil {
return
}
br.Err = b.Verify()
}
@ -227,5 +230,8 @@ func (b *Block) UnmarshalJSON(data []byte) error {
b.Base = *base
b.Transactions = auxb.Transactions
b.ConsensusData = auxb.ConsensusData
// Some tests rely on hash presence and we're usually precomputing
// other hashes upon deserialization.
_ = b.ConsensusData.Hash()
return nil
}

View file

@ -5,6 +5,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert"
)
@ -84,7 +85,7 @@ func newDumbBlock() *Block {
Nonce: 1111,
},
Transactions: []*transaction.Transaction{
transaction.NewIssueTX(),
transaction.New([]byte{byte(opcode.PUSH1)}, 0),
},
}
}

View file

@ -2,7 +2,6 @@ package core
import (
"fmt"
"math"
"math/big"
"sort"
"sync"
@ -33,13 +32,6 @@ const (
headerBatchCount = 2000
version = "0.1.0"
// 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
// like 2000000 is coming from the decrementInterval, but C# code doesn't
// contain any relationship between the two, so we should follow this
// behavior.
registeredAssetLifetime = 2 * 2000000
defaultMemPoolSize = 50000
)
@ -370,20 +362,18 @@ func (bc *Blockchain) notificationDispatcher() {
if len(txFeed) != 0 || len(notificationFeed) != 0 || len(executionFeed) != 0 {
var aerIdx int
for _, tx := range event.block.Transactions {
if tx.Type == transaction.InvocationType {
aer := event.appExecResults[aerIdx]
if !aer.TxHash.Equals(tx.Hash()) {
panic("inconsistent application execution results")
}
aerIdx++
for ch := range executionFeed {
ch <- aer
}
if aer.VMState == "HALT" {
for i := range aer.Events {
for ch := range notificationFeed {
ch <- &aer.Events[i]
}
aer := event.appExecResults[aerIdx]
if !aer.TxHash.Equals(tx.Hash()) {
panic("inconsistent application execution results")
}
aerIdx++
for ch := range executionFeed {
ch <- aer
}
if aer.VMState == "HALT" {
for i := range aer.Events {
for ch := range notificationFeed {
ch <- &aer.Events[i]
}
}
}
@ -536,7 +526,6 @@ func (bc *Blockchain) processHeader(h *block.Header, batch storage.Batch, header
}
buf.Reset()
buf.BinWriter.WriteU32LE(0) // sys fee is yet to be calculated
h.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
@ -549,13 +538,6 @@ func (bc *Blockchain) processHeader(h *block.Header, batch storage.Batch, header
return nil
}
// getSystemFeeAmount 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
// 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
@ -563,11 +545,7 @@ func (bc *Blockchain) getSystemFeeAmount(h util.Uint256) uint32 {
func (bc *Blockchain) storeBlock(block *block.Block) error {
cache := dao.NewCached(bc.dao)
appExecResults := make([]*state.AppExecResult, 0, len(block.Transactions))
fee := bc.getSystemFeeAmount(block.PrevHash)
for _, tx := range block.Transactions {
fee += uint32(tx.SystemFee.IntegralValue())
}
if err := cache.StoreAsBlock(block, fee); err != nil {
if err := cache.StoreAsBlock(block); err != nil {
return err
}
@ -580,226 +558,75 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return err
}
if err := cache.PutUnspentCoinState(tx.Hash(), state.NewUnspentCoin(block.Index, tx)); err != nil {
return err
systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx)
v := SpawnVM(systemInterop)
v.LoadScript(tx.Script)
v.SetPriceGetter(getPrice)
if bc.config.FreeGasLimit > 0 {
v.SetGasLimit(bc.config.FreeGasLimit + tx.SystemFee)
}
// Process TX outputs.
if err := processOutputs(tx, cache); err != nil {
return err
}
// Process TX inputs that are grouped by previous hash.
for _, inputs := range transaction.GroupInputsByPrevHash(tx.Inputs) {
prevHash := inputs[0].PrevHash
unspent, err := cache.GetUnspentCoinState(prevHash)
err := v.Run()
if !v.HasFailed() {
_, err := systemInterop.DAO.Persist()
if err != nil {
return err
return errors.Wrap(err, "failed to persist invocation results")
}
for _, input := range inputs {
if len(unspent.States) <= int(input.PrevIndex) {
return fmt.Errorf("bad input: %s/%d", input.PrevHash.StringLE(), input.PrevIndex)
for _, note := range systemInterop.Notifications {
arr, ok := note.Item.Value().([]vm.StackItem)
if !ok || len(arr) != 4 {
continue
}
if unspent.States[input.PrevIndex].State&state.CoinSpent != 0 {
return fmt.Errorf("double spend: %s/%d", input.PrevHash.StringLE(), input.PrevIndex)
op, ok := arr[0].Value().([]byte)
if !ok || (string(op) != "transfer" && string(op) != "Transfer") {
continue
}
unspent.States[input.PrevIndex].State |= state.CoinSpent
unspent.States[input.PrevIndex].SpendHeight = block.Index
prevTXOutput := &unspent.States[input.PrevIndex].Output
account, err := cache.GetAccountStateOrNew(prevTXOutput.ScriptHash)
if err != nil {
return err
}
if prevTXOutput.AssetID.Equals(GoverningTokenID()) {
err = account.Unclaimed.Put(&state.UnclaimedBalance{
Tx: input.PrevHash,
Index: input.PrevIndex,
Start: unspent.Height,
End: block.Index,
Value: prevTXOutput.Amount,
})
if err != nil {
return err
}
}
balancesLen := len(account.Balances[prevTXOutput.AssetID])
if balancesLen <= 1 {
delete(account.Balances, prevTXOutput.AssetID)
} else {
var index = -1
for i, balance := range account.Balances[prevTXOutput.AssetID] {
if balance.Tx.Equals(input.PrevHash) && balance.Index == input.PrevIndex {
index = i
break
}
}
if index >= 0 {
last := balancesLen - 1
if last > index {
account.Balances[prevTXOutput.AssetID][index] = account.Balances[prevTXOutput.AssetID][last]
}
account.Balances[prevTXOutput.AssetID] = account.Balances[prevTXOutput.AssetID][:last]
}
}
if err = cache.PutAccountState(account); err != nil {
return err
}
}
if err = cache.PutUnspentCoinState(prevHash, unspent); err != nil {
return err
}
}
// Process the underlying type of the TX.
switch t := tx.Data.(type) {
case *transaction.RegisterTX:
err := cache.PutAssetState(&state.Asset{
ID: tx.Hash(),
AssetType: t.AssetType,
Name: t.Name,
Amount: t.Amount,
Precision: t.Precision,
Owner: t.Owner,
Admin: t.Admin,
Expiration: bc.BlockHeight() + registeredAssetLifetime,
})
if err != nil {
return err
}
case *transaction.IssueTX:
for _, res := range bc.GetTransactionResults(tx) {
if res.Amount < 0 {
asset, err := cache.GetAssetState(res.AssetID)
if asset == nil || err != nil {
return fmt.Errorf("issue failed: no asset %s or error %s", res.AssetID, err)
}
asset.Available -= res.Amount
if err := cache.PutAssetState(asset); err != nil {
return err
}
}
}
case *transaction.ClaimTX:
// Remove claimed NEO from spent coins making it unavalaible for
// additional claims.
for _, input := range t.Claims {
scs, err := cache.GetUnspentCoinState(input.PrevHash)
if err == nil {
if len(scs.States) <= int(input.PrevIndex) {
err = errors.New("invalid claim index")
} else if scs.States[input.PrevIndex].State&state.CoinClaimed != 0 {
err = errors.New("double claim")
}
}
if err != nil {
// We can't really do anything about it
// as it's a transaction in a signed block.
bc.log.Warn("FALSE OR DOUBLE CLAIM",
zap.String("PrevHash", input.PrevHash.StringLE()),
zap.Uint16("PrevIndex", input.PrevIndex),
zap.String("tx", tx.Hash().StringLE()),
zap.Uint32("block", block.Index),
)
// "Strict" mode.
if bc.config.VerifyTransactions {
return err
}
break
}
acc, err := cache.GetAccountState(scs.States[input.PrevIndex].ScriptHash)
if err != nil {
return err
}
scs.States[input.PrevIndex].State |= state.CoinClaimed
if err = cache.PutUnspentCoinState(input.PrevHash, scs); err != nil {
return err
}
changed := acc.Unclaimed.Remove(input.PrevHash, input.PrevIndex)
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
}
}
case *transaction.InvocationTX:
systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx)
v := SpawnVM(systemInterop)
v.LoadScript(t.Script)
v.SetPriceGetter(getPrice)
if bc.config.FreeGasLimit > 0 {
v.SetGasLimit(bc.config.FreeGasLimit + t.Gas)
}
err := v.Run()
if !v.HasFailed() {
_, err := systemInterop.DAO.Persist()
if err != nil {
return errors.Wrap(err, "failed to persist invocation results")
}
for _, note := range systemInterop.Notifications {
arr, ok := note.Item.Value().([]vm.StackItem)
if !ok || len(arr) != 4 {
continue
}
op, ok := arr[0].Value().([]byte)
if !ok || (string(op) != "transfer" && string(op) != "Transfer") {
continue
}
var from []byte
fromValue := arr[1].Value()
// we don't have `from` set when we are minting tokens
if fromValue != nil {
from, ok = fromValue.([]byte)
if !ok {
continue
}
}
var to []byte
toValue := arr[2].Value()
// we don't have `to` set when we are burning tokens
if toValue != nil {
to, ok = toValue.([]byte)
if !ok {
continue
}
}
amount, ok := arr[3].Value().(*big.Int)
var from []byte
fromValue := arr[1].Value()
// we don't have `from` set when we are minting tokens
if fromValue != nil {
from, ok = fromValue.([]byte)
if !ok {
bs, ok := arr[3].Value().([]byte)
if !ok {
continue
}
amount = emit.BytesToInt(bs)
continue
}
bc.processNEP5Transfer(cache, tx, block, note.ScriptHash, from, to, amount.Int64())
}
} else {
bc.log.Warn("contract invocation failed",
zap.String("tx", tx.Hash().StringLE()),
zap.Uint32("block", block.Index),
zap.Error(err))
}
aer := &state.AppExecResult{
TxHash: tx.Hash(),
Trigger: trigger.Application,
VMState: v.State(),
GasConsumed: v.GasConsumed(),
Stack: v.Estack().ToContractParameters(),
Events: systemInterop.Notifications,
}
appExecResults = append(appExecResults, aer)
err = cache.PutAppExecResult(aer)
if err != nil {
return errors.Wrap(err, "failed to Store notifications")
var to []byte
toValue := arr[2].Value()
// we don't have `to` set when we are burning tokens
if toValue != nil {
to, ok = toValue.([]byte)
if !ok {
continue
}
}
amount, ok := arr[3].Value().(*big.Int)
if !ok {
bs, ok := arr[3].Value().([]byte)
if !ok {
continue
}
amount = emit.BytesToInt(bs)
}
bc.processNEP5Transfer(cache, tx, block, note.ScriptHash, from, to, amount.Int64())
}
} else {
bc.log.Warn("contract invocation failed",
zap.String("tx", tx.Hash().StringLE()),
zap.Uint32("block", block.Index),
zap.Error(err))
}
aer := &state.AppExecResult{
TxHash: tx.Hash(),
Trigger: trigger.Application,
VMState: v.State(),
GasConsumed: v.GasConsumed(),
Stack: v.Estack().ToContractParameters(),
Events: systemInterop.Notifications,
}
appExecResults = append(appExecResults, aer)
err = cache.PutAppExecResult(aer)
if err != nil {
return errors.Wrap(err, "failed to Store notifications")
}
}
@ -927,7 +754,22 @@ func (bc *Blockchain) GetNEP5Balances(acc util.Uint160) *state.NEP5Balances {
// GetUtilityTokenBalance returns utility token (GAS) balance for the acc.
func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) util.Fixed8 {
return util.Fixed8FromInt64(bc.GetNEP5Balances(acc).Trackers[bc.contracts.GAS.Hash].Balance)
bs, err := bc.dao.GetNEP5Balances(acc)
if err != nil {
return 0
}
return util.Fixed8(bs.Trackers[bc.contracts.GAS.Hash].Balance)
}
// GetGoverningTokenBalance returns governing token (NEO) balance and the height
// of the last balance change for the account.
func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (util.Fixed8, uint32) {
bs, err := bc.dao.GetNEP5Balances(acc)
if err != nil {
return 0, 0
}
neo := bs.Trackers[bc.contracts.NEO.Hash]
return util.Fixed8(neo.Balance), neo.LastUpdatedBlock
}
// LastBatch returns last persisted storage batch.
@ -935,25 +777,6 @@ func (bc *Blockchain) LastBatch() *storage.MemBatch {
return bc.lastBatch
}
// processOutputs processes transaction outputs.
func processOutputs(tx *transaction.Transaction, dao *dao.Cached) error {
for index, output := range tx.Outputs {
account, err := dao.GetAccountStateOrNew(output.ScriptHash)
if err != nil {
return err
}
account.Balances[output.AssetID] = append(account.Balances[output.AssetID], state.UnspentBalance{
Tx: tx.Hash(),
Index: uint16(index),
Value: output.Amount,
})
if err = dao.PutAccountState(account); err != nil {
return err
}
}
return nil
}
// persist flushes current in-memory Store contents to the persistent storage.
func (bc *Blockchain) persist() error {
var (
@ -1002,7 +825,7 @@ func (bc *Blockchain) headerListLen() (n int) {
// GetTransaction returns a TX and its height by the given hash.
func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) {
if tx, _, ok := bc.memPool.TryGetValue(hash); ok {
if tx, ok := bc.memPool.TryGetValue(hash); ok {
return tx, 0, nil // the height is not actually defined for memPool transaction. Not sure if zero is a good number in this case.
}
return bc.dao.GetTransaction(hash)
@ -1033,7 +856,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 {
return nil, err
}
@ -1055,7 +878,7 @@ func (bc *Blockchain) GetHeader(hash util.Uint256) (*block.Header, error) {
return tb.Header(), nil
}
}
block, _, err := bc.dao.GetBlock(hash)
block, err := bc.dao.GetBlock(hash)
if err != nil {
return nil, err
}
@ -1115,17 +938,6 @@ func (bc *Blockchain) HeaderHeight() uint32 {
return uint32(bc.headerListLen() - 1)
}
// GetAssetState returns asset state from its assetID.
func (bc *Blockchain) GetAssetState(assetID util.Uint256) *state.Asset {
asset, err := bc.dao.GetAssetState(assetID)
if asset == nil && err != storage.ErrKeyNotFound {
bc.log.Warn("failed to get asset state",
zap.Stringer("asset", assetID),
zap.Error(err))
}
return asset
}
// GetContractState returns contract by its script hash.
func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract {
contract, err := bc.dao.GetContractState(hash)
@ -1144,15 +956,6 @@ func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *state.Account {
return as
}
// GetUnspentCoinState returns unspent coin state for given tx hash.
func (bc *Blockchain) GetUnspentCoinState(hash util.Uint256) *state.UnspentCoin {
ucs, err := bc.dao.GetUnspentCoinState(hash)
if ucs == nil && err != storage.ErrKeyNotFound {
bc.log.Warn("failed to get unspent coin state", zap.Error(err))
}
return ucs
}
// GetConfig returns the config stored in the blockchain.
func (bc *Blockchain) GetConfig() config.ProtocolConfiguration {
return bc.config
@ -1220,10 +1023,11 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult)
bc.unsubCh <- ch
}
// 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) {
// CalculateClaimable calculates the amount of GAS generated by owning specified
// amount of NEO between specified blocks. The amount of NEO being passed is in
// its natural non-divisible form (1 NEO as 1, 2 NEO as 2, no multiplication by
// 10⁸ is neeeded as for Fixed8).
func (bc *Blockchain) CalculateClaimable(value int64, startHeight, endHeight uint32) util.Fixed8 {
var amount util.Fixed8
di := uint32(bc.decrementInterval)
@ -1249,47 +1053,7 @@ func (bc *Blockchain) CalculateClaimable(value util.Fixed8, startHeight, endHeig
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
// joining each Input with the corresponding Output.
// @TODO: unfortunately we couldn't attach this method to the Transaction struct in the
// transaction package because of a import cycle problem. Perhaps we should think to re-design
// the code base to avoid this situation.
func (bc *Blockchain) References(t *transaction.Transaction) ([]transaction.InOut, error) {
return bc.references(t.Inputs)
}
// references is an internal implementation of References that operates directly
// on a slice of Input.
func (bc *Blockchain) references(ins []transaction.Input) ([]transaction.InOut, error) {
references := make([]transaction.InOut, 0, len(ins))
for _, inputs := range transaction.GroupInputsByPrevHash(ins) {
prevHash := inputs[0].PrevHash
unspent, err := bc.dao.GetUnspentCoinState(prevHash)
if err != nil {
return nil, errors.New("bad input reference")
}
for _, in := range inputs {
if int(in.PrevIndex) > len(unspent.States)-1 {
return nil, errors.New("bad input reference")
}
references = append(references, transaction.InOut{In: *in, Out: unspent.States[in.PrevIndex].Output})
}
}
return references, nil
return amount * util.Fixed8(value)
}
// FeePerByte returns transaction network fee per byte.
@ -1311,14 +1075,14 @@ func (bc *Blockchain) GetMemPool() *mempool.Pool {
// ApplyPolicyToTxSet applies configured policies to given transaction set. It
// expects slice to be ordered by fee and returns a subslice of it.
func (bc *Blockchain) ApplyPolicyToTxSet(txes []mempool.TxWithFee) []mempool.TxWithFee {
func (bc *Blockchain) ApplyPolicyToTxSet(txes []*transaction.Transaction) []*transaction.Transaction {
if bc.config.MaxTransactionsPerBlock != 0 && len(txes) > bc.config.MaxTransactionsPerBlock {
txes = txes[:bc.config.MaxTransactionsPerBlock]
}
maxFree := bc.config.MaxFreeTransactionsPerBlock
if maxFree != 0 {
lowStart := sort.Search(len(txes), func(i int) bool {
return bc.IsLowPriority(txes[i].Fee)
return bc.IsLowPriority(txes[i].NetworkFee)
})
if lowStart+maxFree < len(txes) {
txes = txes[:lowStart+maxFree]
@ -1360,28 +1124,11 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
if netFee < 0 {
return errors.Errorf("insufficient funds: net fee is %v, need %v", t.NetworkFee, needNetworkFee)
}
if transaction.HaveDuplicateInputs(t.Inputs) {
return errors.New("invalid transaction's inputs")
}
if block == nil {
if ok := bc.memPool.Verify(t, bc); !ok {
return errors.New("invalid transaction due to conflicts with the memory pool")
}
}
if bc.dao.IsDoubleSpend(t) {
return errors.New("invalid transaction caused by double spending")
}
if err := bc.verifyOutputs(t); err != nil {
return errors.Wrap(err, "wrong outputs")
}
refs, err := bc.References(t)
if err != nil {
return err
}
results := refsAndOutsToResults(refs, t.Outputs)
if err := bc.verifyResults(t, results); err != nil {
return err
}
for _, a := range t.Attributes {
if a.Usage == transaction.ECDH02 || a.Usage == transaction.ECDH03 {
@ -1389,96 +1136,9 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
}
}
switch t.Type {
case transaction.ClaimType:
claim := t.Data.(*transaction.ClaimTX)
if transaction.HaveDuplicateInputs(claim.Claims) {
return errors.New("duplicate claims")
}
if bc.dao.IsDoubleClaim(claim) {
return errors.New("double claim")
}
if err := bc.verifyClaims(t, results); err != nil {
return err
}
case transaction.InvocationType:
inv := t.Data.(*transaction.InvocationTX)
if inv.Gas.FractionalValue() != 0 {
return errors.New("invocation gas can only be integer")
}
}
return bc.verifyTxWitnesses(t, block)
}
func (bc *Blockchain) verifyClaims(tx *transaction.Transaction, results []*transaction.Result) (err error) {
t := tx.Data.(*transaction.ClaimTX)
var result *transaction.Result
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
unspent, err := bc.dao.GetUnspentCoinState(h)
if err != nil {
return 0, err
}
for _, c := range group {
if len(unspent.States) <= int(c.PrevIndex) {
return 0, fmt.Errorf("can't find spent coins for %s (%d)", c.PrevHash.StringLE(), c.PrevIndex)
}
if unspent.States[c.PrevIndex].State&state.CoinSpent == 0 {
return 0, fmt.Errorf("not spent yet: %s/%d", c.PrevHash.StringLE(), c.PrevIndex)
}
if unspent.States[c.PrevIndex].State&state.CoinClaimed != 0 {
return 0, fmt.Errorf("already claimed: %s/%d", c.PrevHash.StringLE(), c.PrevIndex)
}
unclaimed = append(unclaimed, &spentCoin{
Output: &unspent.States[c.PrevIndex].Output,
StartHeight: unspent.Height,
EndHeight: unspent.States[c.PrevIndex].SpendHeight,
})
}
}
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
}
// isTxStillRelevant is a callback for mempool transaction filtering after 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
@ -1492,15 +1152,6 @@ func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction) bool {
if bc.dao.HasTransaction(t.Hash()) {
return false
}
if bc.dao.IsDoubleSpend(t) {
return false
}
if t.Type == transaction.ClaimType {
claim := t.Data.(*transaction.ClaimTX)
if bc.dao.IsDoubleClaim(claim) {
return false
}
}
for i := range t.Scripts {
if !vm.IsStandardContract(t.Scripts[i].VerificationScript) {
recheckWitness = true
@ -1536,14 +1187,12 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
return err
}
// Policying.
if t.Type != transaction.ClaimType {
txSize := io.GetVarSize(t)
maxFree := bc.config.MaxFreeTransactionSize
if maxFree != 0 && txSize > maxFree {
if bc.IsLowPriority(t.NetworkFee) ||
t.NetworkFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
return ErrPolicy
}
txSize := io.GetVarSize(t)
maxFree := bc.config.MaxFreeTransactionSize
if maxFree != 0 && txSize > maxFree {
if bc.IsLowPriority(t.NetworkFee) ||
t.NetworkFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) {
return ErrPolicy
}
}
if err := bc.memPool.Add(t, bc); err != nil {
@ -1559,125 +1208,6 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
return nil
}
func (bc *Blockchain) verifyOutputs(t *transaction.Transaction) error {
for assetID, outputs := range t.GroupOutputByAssetID() {
assetState := bc.GetAssetState(assetID)
if assetState == nil {
return fmt.Errorf("no asset state for %s", assetID.StringLE())
}
if assetState.Expiration < bc.blockHeight+1 && assetState.AssetType != transaction.GoverningToken && assetState.AssetType != transaction.UtilityToken {
return fmt.Errorf("asset %s expired", assetID.StringLE())
}
for _, out := range outputs {
if int64(out.Amount)%int64(math.Pow10(8-int(assetState.Precision))) != 0 {
return fmt.Errorf("output is not compliant with %s asset precision", assetID.StringLE())
}
}
}
return nil
}
func (bc *Blockchain) verifyResults(t *transaction.Transaction, results []*transaction.Result) error {
var resultsDestroy []*transaction.Result
var resultsIssue []*transaction.Result
for _, re := range results {
if re.Amount.GreaterThan(util.Fixed8(0)) {
resultsDestroy = append(resultsDestroy, re)
}
if re.Amount.LessThan(util.Fixed8(0)) {
resultsIssue = append(resultsIssue, re)
}
}
if len(resultsDestroy) > 1 {
return errors.New("tx has more than 1 destroy output")
}
if len(resultsDestroy) == 1 && resultsDestroy[0].AssetID != UtilityTokenID() {
return errors.New("tx destroys non-utility token")
}
sysfee := t.SystemFee
if sysfee.GreaterThan(util.Fixed8(0)) {
if len(resultsDestroy) == 0 {
return fmt.Errorf("system requires to pay %s fee, but tx pays nothing", sysfee.String())
}
if resultsDestroy[0].Amount.LessThan(sysfee) {
return fmt.Errorf("system requires to pay %s fee, but tx pays %s only", sysfee.String(), resultsDestroy[0].Amount.String())
}
}
switch t.Type {
case transaction.ClaimType:
for _, r := range resultsIssue {
if r.AssetID != UtilityTokenID() {
return errors.New("miner or claim tx issues non-utility tokens")
}
}
break
case transaction.IssueType:
for _, r := range resultsIssue {
if r.AssetID == UtilityTokenID() {
return errors.New("issue tx issues utility tokens")
}
asset, err := bc.dao.GetAssetState(r.AssetID)
if asset == nil || err != nil {
return errors.New("invalid asset in issue tx")
}
if asset.Available < r.Amount {
return errors.New("trying to issue more than available")
}
}
break
default:
if len(resultsIssue) > 0 {
return errors.New("non issue/miner/claim tx issues tokens")
}
break
}
return nil
}
// GetTransactionResults returns the transaction results aggregate by assetID.
// Golang of GetTransationResults method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L207)
func (bc *Blockchain) GetTransactionResults(t *transaction.Transaction) []*transaction.Result {
references, err := bc.References(t)
if err != nil {
return nil
}
return refsAndOutsToResults(references, t.Outputs)
}
// mapReferencesToResults returns cumulative results of transaction based in its
// references and outputs.
func refsAndOutsToResults(references []transaction.InOut, outputs []transaction.Output) []*transaction.Result {
var results []*transaction.Result
tempResult := make(map[util.Uint256]util.Fixed8)
for _, inout := range references {
c := tempResult[inout.Out.AssetID]
tempResult[inout.Out.AssetID] = c.Add(inout.Out.Amount)
}
for _, output := range outputs {
c := tempResult[output.AssetID]
tempResult[output.AssetID] = c.Sub(output.Amount)
}
results = []*transaction.Result{} // this assignment is necessary. (Most of the time amount == 0 and results is the empty slice.)
for assetID, amount := range tempResult {
if amount != util.Fixed8(0) {
results = append(results, &transaction.Result{
AssetID: assetID,
Amount: amount,
})
}
}
return results
}
//GetStandByValidators returns validators from the configuration.
func (bc *Blockchain) GetStandByValidators() (keys.PublicKeys, error) {
return getValidators(bc.config)
@ -1697,57 +1227,11 @@ func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) {
// to verify whether the transaction is bonafide or not.
// Golang implementation of GetScriptHashesForVerifying method in C# (https://github.com/neo-project/neo/blob/master/neo/Network/P2P/Payloads/Transaction.cs#L190)
func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([]util.Uint160, error) {
references, err := bc.References(t)
if err != nil {
return nil, err
}
hashes := make(map[util.Uint160]bool)
for i := range references {
hashes[references[i].Out.ScriptHash] = true
}
for a, outputs := range t.GroupOutputByAssetID() {
as := bc.GetAssetState(a)
if as == nil {
return nil, errors.New("Invalid operation")
}
if as.AssetType&transaction.DutyFlag != 0 {
for _, o := range outputs {
h := o.ScriptHash
if _, ok := hashes[h]; !ok {
hashes[h] = true
}
}
}
}
hashes[t.Sender] = true
for _, c := range t.Cosigners {
hashes[c.Account] = true
}
switch t.Type {
case transaction.ClaimType:
claim := t.Data.(*transaction.ClaimTX)
refs, err := bc.references(claim.Claims)
if err != nil {
return nil, err
}
for i := range refs {
hashes[refs[i].Out.ScriptHash] = true
}
case transaction.IssueType:
for _, res := range refsAndOutsToResults(references, t.Outputs) {
if res.Amount < 0 {
asset, err := bc.dao.GetAssetState(res.AssetID)
if asset == nil || err != nil {
return nil, errors.New("invalid asset in issue tx")
}
hashes[asset.Issuer] = true
}
}
case transaction.RegisterType:
reg := t.Data.(*transaction.RegisterTX)
hashes[reg.Owner.GetScriptHash()] = true
}
// convert hashes to []util.Uint160
hashesResult := make([]util.Uint160, 0, len(hashes))
for h := range hashes {
@ -1830,7 +1314,6 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
// is used for easy interop access and can be omitted for transactions that are
// not yet added into any block.
// Golang implementation of VerifyWitnesses method in C# (https://github.com/neo-project/neo/blob/master/neo/SmartContract/Helper.cs#L87).
// Unfortunately the IVerifiable interface could not be implemented because we can't move the References method in blockchain.go to the transaction.go file.
func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block.Block) error {
hashes, err := bc.GetScriptHashesForVerifying(t)
if err != nil {

View file

@ -102,7 +102,7 @@ func TestScriptFromWitness(t *testing.T) {
func TestGetHeader(t *testing.T) {
bc := newTestChain(t)
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.ValidUntilBlock = bc.BlockHeight() + 1
assert.Nil(t, addSender(tx))
assert.Nil(t, signTx(bc, tx))
@ -194,31 +194,23 @@ func TestGetClaimable(t *testing.T) {
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)
amount := bc.CalculateClaimable(1, 0, 2)
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)
amount := bc.CalculateClaimable(1, 0, 6)
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)
amount := bc.CalculateClaimable(1, 1, 7)
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)
amount := bc.CalculateClaimable(1, 1, 10)
require.EqualValues(t, 4+3+3+2+2+1+1, amount)
require.EqualValues(t, 0, sysfee)
})
}
@ -275,7 +267,7 @@ func TestSubscriptions(t *testing.T) {
emit.Bytes(script.BinWriter, []byte("yay!"))
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
require.NoError(t, script.Err)
txGood1 := transaction.NewInvocationTX(script.Bytes(), 0)
txGood1 := transaction.New(script.Bytes(), 0)
txGood1.Sender = neoOwner
txGood1.Nonce = 1
txGood1.ValidUntilBlock = 100500
@ -287,7 +279,7 @@ func TestSubscriptions(t *testing.T) {
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
emit.Opcode(script.BinWriter, opcode.THROW)
require.NoError(t, script.Err)
txBad := transaction.NewInvocationTX(script.Bytes(), 0)
txBad := transaction.New(script.Bytes(), 0)
txBad.Sender = neoOwner
txBad.Nonce = 2
txBad.ValidUntilBlock = 100500
@ -297,7 +289,7 @@ func TestSubscriptions(t *testing.T) {
emit.Bytes(script.BinWriter, []byte("yay! yay! yay!"))
emit.Syscall(script.BinWriter, "Neo.Runtime.Notify")
require.NoError(t, script.Err)
txGood2 := transaction.NewInvocationTX(script.Bytes(), 0)
txGood2 := transaction.New(script.Bytes(), 0)
txGood2.Sender = neoOwner
txGood2.Nonce = 3
txGood2.ValidUntilBlock = 100500
@ -319,14 +311,11 @@ func TestSubscriptions(t *testing.T) {
for _, txExpected := range invBlock.Transactions {
tx := <-txCh
require.Equal(t, txExpected, tx)
if txExpected.Type == transaction.InvocationType {
exec := <-executionCh
require.Equal(t, tx.Hash(), exec.TxHash)
if exec.VMState == "HALT" {
notif := <-notificationCh
inv := tx.Data.(*transaction.InvocationTX)
require.Equal(t, hash.Hash160(inv.Script), notif.ScriptHash)
}
exec := <-executionCh
require.Equal(t, tx.Hash(), exec.TxHash)
if exec.VMState == "HALT" {
notif := <-notificationCh
require.Equal(t, hash.Hash160(tx.Script), notif.ScriptHash)
}
}
assert.Empty(t, txCh)

View file

@ -14,24 +14,24 @@ import (
// Blockchainer is an interface that abstract the implementation
// of the blockchain.
type Blockchainer interface {
ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithFee
ApplyPolicyToTxSet([]*transaction.Transaction) []*transaction.Transaction
GetConfig() config.ProtocolConfiguration
AddHeaders(...*block.Header) error
AddBlock(*block.Block) error
BlockHeight() uint32
CalculateClaimable(value util.Fixed8, startHeight, endHeight uint32) (util.Fixed8, util.Fixed8, error)
CalculateClaimable(value int64, startHeight, endHeight uint32) util.Fixed8
Close()
HeaderHeight() uint32
GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) *state.Contract
GetEnrollments() ([]state.Validator, error)
GetGoverningTokenBalance(acc util.Uint160) (util.Fixed8, uint32)
GetHeaderHash(int) util.Uint256
GetHeader(hash util.Uint256) (*block.Header, error)
CurrentHeaderHash() util.Uint256
CurrentBlockHash() util.Uint256
HasBlock(util.Uint256) bool
HasTransaction(util.Uint256) bool
GetAssetState(util.Uint256) *state.Asset
GetAccountState(util.Uint160) *state.Account
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
GetNEP5TransferLog(util.Uint160) *state.NEP5TransferLog
@ -43,8 +43,6 @@ type Blockchainer interface {
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetTestVM() *vm.VM
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
GetUnspentCoinState(util.Uint256) *state.UnspentCoin
References(t *transaction.Transaction) ([]transaction.InOut, error)
mempool.Feer // fee interface
PoolTx(*transaction.Transaction) error
SubscribeForBlocks(ch chan<- *block.Block)

View file

@ -15,7 +15,6 @@ type Cached struct {
DAO
accounts map[util.Uint160]*state.Account
contracts map[util.Uint160]*state.Contract
unspents map[util.Uint256]*state.UnspentCoin
balances map[util.Uint160]*state.NEP5Balances
transfers map[util.Uint160]map[uint32]*state.NEP5TransferLog
}
@ -24,10 +23,9 @@ type Cached struct {
func NewCached(d DAO) *Cached {
accs := make(map[util.Uint160]*state.Account)
ctrs := make(map[util.Uint160]*state.Contract)
unspents := make(map[util.Uint256]*state.UnspentCoin)
balances := make(map[util.Uint160]*state.NEP5Balances)
transfers := make(map[util.Uint160]map[uint32]*state.NEP5TransferLog)
return &Cached{d.GetWrapped(), accs, ctrs, unspents, balances, transfers}
return &Cached{d.GetWrapped(), accs, ctrs, balances, transfers}
}
// GetAccountStateOrNew retrieves Account from cache or underlying store
@ -77,20 +75,6 @@ func (cd *Cached) DeleteContractState(hash util.Uint160) error {
return cd.DAO.DeleteContractState(hash)
}
// GetUnspentCoinState retrieves UnspentCoin from cache or underlying store.
func (cd *Cached) GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error) {
if cd.unspents[hash] != nil {
return cd.unspents[hash], nil
}
return cd.DAO.GetUnspentCoinState(hash)
}
// PutUnspentCoinState saves given UnspentCoin in the cache.
func (cd *Cached) PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error {
cd.unspents[hash] = ucs
return nil
}
// GetNEP5Balances retrieves NEP5Balances for the acc.
func (cd *Cached) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) {
if bs := cd.balances[acc]; bs != nil {
@ -167,13 +151,6 @@ func (cd *Cached) Persist() (int, error) {
}
buf.Reset()
}
for hash := range cd.unspents {
err := cd.DAO.putUnspentCoinState(hash, cd.unspents[hash], buf)
if err != nil {
return 0, err
}
buf.Reset()
}
for acc, bs := range cd.balances {
err := cd.DAO.putNEP5Balances(acc, bs, buf)
if err != nil {
@ -197,7 +174,6 @@ func (cd *Cached) GetWrapped() DAO {
return &Cached{cd.DAO.GetWrapped(),
cd.accounts,
cd.contracts,
cd.unspents,
cd.balances,
cd.transfers,
}

View file

@ -23,9 +23,8 @@ type DAO interface {
GetAccountStateOrNew(hash util.Uint160) (*state.Account, error)
GetAndDecode(entity io.Serializable, key []byte) error
GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error)
GetAssetState(assetID util.Uint256) (*state.Asset, error)
GetBatch() *storage.MemBatch
GetBlock(hash util.Uint256) (*block.Block, uint32, error)
GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) (*state.Contract, error)
GetCurrentBlockHeight() (uint32, error)
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error)
@ -36,29 +35,23 @@ type DAO interface {
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error)
GetVersion() (string, error)
GetWrapped() DAO
HasTransaction(hash util.Uint256) bool
IsDoubleClaim(claim *transaction.ClaimTX) bool
IsDoubleSpend(tx *transaction.Transaction) bool
Persist() (int, error)
PutAccountState(as *state.Account) error
PutAppExecResult(aer *state.AppExecResult) error
PutAssetState(as *state.Asset) error
PutContractState(cs *state.Contract) error
PutCurrentHeader(hashAndIndex []byte) error
PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error
PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error
PutVersion(v string) error
StoreAsBlock(block *block.Block, sysFee uint32) error
StoreAsBlock(block *block.Block) error
StoreAsCurrentBlock(block *block.Block) error
StoreAsTransaction(tx *transaction.Transaction, index uint32) error
putAccountState(as *state.Account, buf *io.BufBinWriter) error
putNEP5Balances(acc util.Uint160, bs *state.NEP5Balances, buf *io.BufBinWriter) error
putUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin, buf *io.BufBinWriter) error
}
// Simple is memCached wrapper around DB, simple DAO implementation.
@ -146,30 +139,6 @@ func (dao *Simple) putAccountState(as *state.Account, buf *io.BufBinWriter) erro
// -- end accounts.
// -- start assets.
// GetAssetState returns given asset state as recorded in the given store.
func (dao *Simple) GetAssetState(assetID util.Uint256) (*state.Asset, error) {
asset := &state.Asset{}
key := storage.AppendPrefix(storage.STAsset, assetID.BytesBE())
err := dao.GetAndDecode(asset, key)
if err != nil {
return nil, err
}
if asset.ID != assetID {
return nil, fmt.Errorf("found asset id is not equal to expected")
}
return asset, nil
}
// PutAssetState puts given asset state into the given store.
func (dao *Simple) PutAssetState(as *state.Asset) error {
key := storage.AppendPrefix(storage.STAsset, as.ID.BytesBE())
return dao.Put(as, key)
}
// -- end assets.
// -- start contracts.
// GetContractState returns contract state as recorded in the given
@ -276,31 +245,6 @@ func (dao *Simple) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.
// -- end transfer log.
// -- start unspent coins.
// GetUnspentCoinState retrieves UnspentCoinState from the given store.
func (dao *Simple) GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error) {
unspent := &state.UnspentCoin{}
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
err := dao.GetAndDecode(unspent, key)
if err != nil {
return nil, err
}
return unspent, nil
}
// PutUnspentCoinState puts given UnspentCoinState into the given store.
func (dao *Simple) PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error {
return dao.putUnspentCoinState(hash, ucs, io.NewBufBinWriter())
}
func (dao *Simple) putUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin, buf *io.BufBinWriter) error {
key := storage.AppendPrefix(storage.STCoin, hash.BytesLE())
return dao.putWithBuffer(ucs, key, buf)
}
// -- end unspent coins.
// -- start notification event.
// GetAppExecResult gets application execution result from the
@ -402,18 +346,18 @@ func makeStorageItemKey(scripthash util.Uint160, key []byte) []byte {
// -- other.
// GetBlock returns Block by the given hash if it exists in the store.
func (dao *Simple) GetBlock(hash util.Uint256) (*block.Block, uint32, error) {
func (dao *Simple) GetBlock(hash util.Uint256) (*block.Block, error) {
key := storage.AppendPrefix(storage.DataBlock, hash.BytesLE())
b, err := dao.Store.Get(key)
if err != nil {
return nil, 0, err
return nil, err
}
block, err := block.NewBlockFromTrimmedBytes(b[4:])
block, err := block.NewBlockFromTrimmedBytes(b)
if err != nil {
return nil, 0, err
return nil, err
}
return block, binary.LittleEndian.Uint32(b[:4]), nil
return block, nil
}
// GetVersion attempts to get the current version stored in the
@ -531,12 +475,11 @@ func (dao *Simple) HasTransaction(hash util.Uint256) bool {
}
// StoreAsBlock stores the given block as DataBlock.
func (dao *Simple) StoreAsBlock(block *block.Block, sysFee uint32) error {
func (dao *Simple) StoreAsBlock(block *block.Block) error {
var (
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
buf = io.NewBufBinWriter()
)
buf.WriteU32LE(sysFee)
b, err := block.Trim()
if err != nil {
return err
@ -569,35 +512,6 @@ func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32)
return dao.Store.Put(key, buf.Bytes())
}
// IsDoubleSpend verifies that the input transactions are not double spent.
func (dao *Simple) IsDoubleSpend(tx *transaction.Transaction) bool {
return dao.checkUsedInputs(tx.Inputs, state.CoinSpent)
}
// IsDoubleClaim verifies that given claim inputs are not already claimed by another tx.
func (dao *Simple) IsDoubleClaim(claim *transaction.ClaimTX) bool {
return dao.checkUsedInputs(claim.Claims, state.CoinClaimed)
}
func (dao *Simple) checkUsedInputs(inputs []transaction.Input, coin state.Coin) bool {
if len(inputs) == 0 {
return false
}
for _, inputs := range transaction.GroupInputsByPrevHash(inputs) {
prevHash := inputs[0].PrevHash
unspent, err := dao.GetUnspentCoinState(prevHash)
if err != nil {
return true
}
for _, input := range inputs {
if int(input.PrevIndex) >= len(unspent.States) || (unspent.States[input.PrevIndex].State&coin) != 0 {
return true
}
}
}
return false
}
// Persist flushes all the changes made into the (supposedly) persistent
// underlying store.
func (dao *Simple) Persist() (int, error) {

View file

@ -7,7 +7,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -59,17 +58,6 @@ func TestPutAndGetAccountStateOrNew(t *testing.T) {
require.Equal(t, accountState.ScriptHash, gotAccount.ScriptHash)
}
func TestPutAndGetAssetState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
id := random.Uint256()
assetState := &state.Asset{ID: id, Owner: keys.PublicKey{}}
err := dao.PutAssetState(assetState)
require.NoError(t, err)
gotAssetState, err := dao.GetAssetState(id)
require.NoError(t, err)
require.Equal(t, assetState, gotAssetState)
}
func TestPutAndGetContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}}
@ -94,25 +82,6 @@ func TestDeleteContractState(t *testing.T) {
require.Nil(t, gotContractState)
}
func TestGetUnspentCoinState_Err(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256()
gotUnspentCoinState, err := dao.GetUnspentCoinState(hash)
require.Error(t, err)
require.Nil(t, gotUnspentCoinState)
}
func TestPutGetUnspentCoinState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256()
unspentCoinState := &state.UnspentCoin{Height: 42, States: []state.OutputState{}}
err := dao.PutUnspentCoinState(hash, unspentCoinState)
require.NoError(t, err)
gotUnspentCoinState, err := dao.GetUnspentCoinState(hash)
require.NoError(t, err)
require.Equal(t, unspentCoinState, gotUnspentCoinState)
}
func TestPutGetAppExecResult(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256()
@ -155,7 +124,7 @@ func TestDeleteStorageItem(t *testing.T) {
func TestGetBlock_NotExists(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256()
block, _, err := dao.GetBlock(hash)
block, err := dao.GetBlock(hash)
require.Error(t, err)
require.Nil(t, block)
}
@ -171,12 +140,11 @@ func TestPutGetBlock(t *testing.T) {
},
}
hash := b.Hash()
err := dao.StoreAsBlock(b, 42)
err := dao.StoreAsBlock(b)
require.NoError(t, err)
gotBlock, sysfee, err := dao.GetBlock(hash)
gotBlock, err := dao.GetBlock(hash)
require.NoError(t, err)
require.NotNil(t, gotBlock)
require.EqualValues(t, 42, sysfee)
}
func TestGetVersion_NoVersion(t *testing.T) {
@ -221,7 +189,7 @@ func TestGetCurrentHeaderHeight_Store(t *testing.T) {
func TestStoreAsTransaction(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
tx := transaction.NewIssueTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1)
hash := tx.Hash()
err := dao.StoreAsTransaction(tx, 0)
require.NoError(t, err)

View file

@ -41,10 +41,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
}
const (
neoAssetCreate = 0x1fc6c583 // Neo.Asset.Create
antSharesAssetCreate = 0x99025068 // AntShares.Asset.Create
neoAssetRenew = 0x71908478 // Neo.Asset.Renew
antSharesAssetRenew = 0xaf22447b // AntShares.Asset.Renew
neoContractCreate = 0x6ea56cf6 // Neo.Contract.Create
neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate
antSharesContractCreate = 0x2a28d29b // AntShares.Contract.Create
@ -58,11 +54,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
estack := v.Estack()
switch id {
case neoAssetCreate, antSharesAssetCreate:
return util.Fixed8FromInt64(5000)
case neoAssetRenew, antSharesAssetRenew:
arg := estack.Peek(1).BigInt().Int64()
return util.Fixed8FromInt64(arg * 5000)
case neoContractCreate, neoContractMigrate, antSharesContractCreate, antSharesContractMigrate:
return smartcontract.GetDeploymentPrice(smartcontract.PropertyState(estack.Peek(3).BigInt().Int64()))
case systemStoragePut, systemStoragePutEx, neoStoragePut, antSharesStoragePut:

View file

@ -23,21 +23,6 @@ func TestGetPrice(t *testing.T) {
v := SpawnVM(systemInterop)
v.SetPriceGetter(getPrice)
t.Run("Neo.Asset.Create", func(t *testing.T) {
// Neo.Asset.Create: 83c5c61f
v.Load([]byte{byte(opcode.SYSCALL), 0x83, 0xc5, 0xc6, 0x1f})
checkGas(t, util.Fixed8FromInt64(5000), v)
})
t.Run("Neo.Asset.Renew", func(t *testing.T) {
// Neo.Asset.Renew: 78849071 (requires push 09 push 09 before)
v.Load([]byte{byte(opcode.PUSH9), byte(opcode.PUSH9), byte(opcode.SYSCALL), 0x78, 0x84, 0x90, 0x71})
require.NoError(t, v.StepInto()) // push 9
require.NoError(t, v.StepInto()) // push 9
checkGas(t, util.Fixed8FromInt64(9*5000), v)
})
t.Run("Neo.Contract.Create (no props)", func(t *testing.T) {
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
v.Load([]byte{byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0),

View file

@ -60,7 +60,7 @@ func newBlock(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256,
Base: block.Base{
Version: 0,
PrevHash: prev,
Timestamp: uint64(time.Now().UTC().Unix()) + uint64(index),
Timestamp: uint64(time.Now().UTC().Unix())*1000 + uint64(index),
Index: index,
NextConsensus: witness.ScriptHash(),
Script: witness,
@ -138,7 +138,7 @@ func newDumbBlock() *block.Block {
Nonce: 1111,
},
Transactions: []*transaction.Transaction{
{Type: transaction.IssueType},
transaction.New([]byte{byte(opcode.PUSH1)}, 0),
},
}
}
@ -165,19 +165,21 @@ func TestCreateBasicChain(t *testing.T) {
return testNonce
}
var neoAmount = util.Fixed8FromInt64(99999000)
var neoRemainder = util.Fixed8FromInt64(100000000) - neoAmount
const neoAmount = 99999000
bc := newTestChain(t)
defer bc.Close()
gasHash := bc.contracts.GAS.Hash
neoHash := bc.contracts.NEO.Hash
t.Logf("native GAS hash: %v", gasHash)
t.Logf("native NEO hash: %v", neoHash)
priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash()
// Move almost all NEO and some nep5 GAS to one simple account.
txMoveNeo := newNEP5Transfer(gasHash, neoOwner, priv0ScriptHash, 1000000000)
require.Equal(t, util.Fixed8FromInt64(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
// Move some NEO to one simple account.
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
txMoveNeo.ValidUntilBlock = validUntilBlock
txMoveNeo.Nonce = getNextNonce()
txMoveNeo.Sender = neoOwner
@ -187,93 +189,34 @@ func TestCreateBasicChain(t *testing.T) {
AllowedContracts: nil,
AllowedGroups: nil,
}}
// use output of issue tx from genesis block as an input
genesisBlock, err := bc.GetBlock(bc.GetHeaderHash(0))
require.NoError(t, err)
require.Equal(t, 4, len(genesisBlock.Transactions))
h := genesisBlock.Transactions[2].Hash()
txMoveNeo.AddInput(&transaction.Input{
PrevHash: h,
PrevIndex: 0,
})
txMoveNeo.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(),
Amount: neoAmount,
ScriptHash: priv0ScriptHash,
Position: 0,
})
txMoveNeo.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(),
Amount: neoRemainder,
ScriptHash: neoOwner,
Position: 1,
})
require.NoError(t, signTx(bc, txMoveNeo))
b := bc.newBlock(txMoveNeo)
// Move some GAS to one simple account.
txMoveGas := newNEP5Transfer(gasHash, neoOwner, priv0ScriptHash, int64(util.Fixed8FromInt64(1000)))
txMoveGas.ValidUntilBlock = validUntilBlock
txMoveGas.Nonce = getNextNonce()
txMoveGas.Sender = neoOwner
txMoveGas.Cosigners = []transaction.Cosigner{{
Account: neoOwner,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
}}
require.NoError(t, signTx(bc, txMoveGas))
b := bc.newBlock(txMoveNeo, txMoveGas)
require.NoError(t, bc.AddBlock(b))
t.Logf("txMoveNeo: %s", txMoveNeo.Hash().StringLE())
t.Logf("txMoveGas: %s", txMoveGas.Hash().StringLE())
require.Equal(t, util.Fixed8FromInt64(1000), bc.GetUtilityTokenBalance(priv0ScriptHash))
// info for getblockheader rpc tests
t.Logf("header hash: %s", b.Hash().StringLE())
buf := io.NewBufBinWriter()
b.Header().EncodeBinary(buf.BinWriter)
t.Logf("header: %s", hex.EncodeToString(buf.Bytes()))
// Generate some blocks to be able to claim GAS for them.
_, err = bc.genBlocks(numOfEmptyBlocks)
require.NoError(t, err)
acc0, err := wallet.NewAccountFromWIF(priv0.WIF())
require.NoError(t, err)
// Make a NEO roundtrip (send to myself) and claim GAS.
txNeoRound := transaction.NewContractTX()
txNeoRound.Nonce = getNextNonce()
txNeoRound.Sender = priv0ScriptHash
txNeoRound.ValidUntilBlock = validUntilBlock
txNeoRound.AddInput(&transaction.Input{
PrevHash: txMoveNeo.Hash(),
PrevIndex: 0,
})
txNeoRound.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(),
Amount: neoAmount,
ScriptHash: priv0.GetScriptHash(),
Position: 0,
})
txNeoRound.Data = new(transaction.ContractTX)
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
require.NoError(t, acc0.SignTx(txNeoRound))
b = bc.newBlock(txNeoRound)
require.NoError(t, bc.AddBlock(b))
t.Logf("txNeoRound: %s", txNeoRound.Hash().StringLE())
claim := new(transaction.ClaimTX)
claim.Claims = append(claim.Claims, transaction.Input{
PrevHash: txMoveNeo.Hash(),
PrevIndex: 0,
})
txClaim := transaction.NewClaimTX(claim)
txClaim.Nonce = getNextNonce()
txClaim.ValidUntilBlock = validUntilBlock
txClaim.Sender = priv0ScriptHash
txClaim.Data = claim
neoGas, sysGas, err := bc.CalculateClaimable(neoAmount, 1, bc.BlockHeight())
require.NoError(t, err)
gasOwned := neoGas + sysGas
txClaim.AddOutput(&transaction.Output{
AssetID: UtilityTokenID(),
Amount: gasOwned,
ScriptHash: priv0.GetScriptHash(),
Position: 0,
})
require.NoError(t, addNetworkFee(bc, txClaim, acc0))
require.NoError(t, acc0.SignTx(txClaim))
b = bc.newBlock(txClaim)
require.NoError(t, bc.AddBlock(b))
t.Logf("txClaim: %s", txClaim.Hash().StringLE())
// Push some contract into the chain.
avm, err := ioutil.ReadFile(prefix + "test_contract.avm")
require.NoError(t, err)
@ -297,21 +240,10 @@ func TestCreateBasicChain(t *testing.T) {
txScript := script.Bytes()
invFee := util.Fixed8FromFloat(100)
txDeploy := transaction.NewInvocationTX(txScript, invFee)
txDeploy := transaction.New(txScript, invFee)
txDeploy.Nonce = getNextNonce()
txDeploy.ValidUntilBlock = validUntilBlock
txDeploy.Sender = priv0ScriptHash
txDeploy.AddInput(&transaction.Input{
PrevHash: txClaim.Hash(),
PrevIndex: 0,
})
txDeploy.AddOutput(&transaction.Output{
AssetID: UtilityTokenID(),
Amount: gasOwned - invFee,
ScriptHash: priv0.GetScriptHash(),
Position: 0,
})
gasOwned -= invFee
require.NoError(t, addNetworkFee(bc, txDeploy, acc0))
require.NoError(t, acc0.SignTx(txDeploy))
b = bc.newBlock(txDeploy)
@ -322,7 +254,7 @@ func TestCreateBasicChain(t *testing.T) {
script = io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "Put", "testkey", "testvalue")
txInv := transaction.NewInvocationTX(script.Bytes(), 0)
txInv := transaction.New(script.Bytes(), 0)
txInv.Nonce = getNextNonce()
txInv.ValidUntilBlock = validUntilBlock
txInv.Sender = priv0ScriptHash
@ -333,26 +265,18 @@ func TestCreateBasicChain(t *testing.T) {
t.Logf("txInv: %s", txInv.Hash().StringLE())
priv1 := testchain.PrivateKeyByID(1)
txNeo0to1 := transaction.NewContractTX()
txNeo0to1 := newNEP5Transfer(neoHash, priv0ScriptHash, priv1.GetScriptHash(), 1000)
txNeo0to1.Nonce = getNextNonce()
txNeo0to1.ValidUntilBlock = validUntilBlock
txNeo0to1.Sender = priv0ScriptHash
txNeo0to1.Data = new(transaction.ContractTX)
txNeo0to1.AddInput(&transaction.Input{
PrevHash: txNeoRound.Hash(),
PrevIndex: 0,
})
txNeo0to1.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(),
Amount: util.Fixed8FromInt64(1000),
ScriptHash: priv1.GetScriptHash(),
})
txNeo0to1.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(),
Amount: neoAmount - util.Fixed8FromInt64(1000),
ScriptHash: priv0.GetScriptHash(),
})
txNeo0to1.Cosigners = []transaction.Cosigner{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0))
require.NoError(t, acc0.SignTx(txNeo0to1))
b = bc.newBlock(txNeo0to1)
@ -361,7 +285,7 @@ func TestCreateBasicChain(t *testing.T) {
sh := hash.Hash160(avm)
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, sh, "init")
initTx := transaction.NewInvocationTX(w.Bytes(), 0)
initTx := transaction.New(w.Bytes(), 0)
initTx.Nonce = getNextNonce()
initTx.ValidUntilBlock = validUntilBlock
initTx.Sender = priv0ScriptHash
@ -384,7 +308,7 @@ func TestCreateBasicChain(t *testing.T) {
b = bc.newBlock(initTx, transferTx)
require.NoError(t, bc.AddBlock(b))
t.Logf("recieveRublesTx: %v", transferTx.Hash().StringBE())
t.Logf("recieveRublesTx: %v", transferTx.Hash().StringLE())
transferTx = newNEP5Transfer(sh, priv0.GetScriptHash(), priv1.GetScriptHash(), 123)
transferTx.Nonce = getNextNonce()
@ -403,7 +327,7 @@ func TestCreateBasicChain(t *testing.T) {
b = bc.newBlock(transferTx)
require.NoError(t, bc.AddBlock(b))
t.Logf("sendRublesTx: %v", transferTx.Hash().StringBE())
t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE())
if saveChain {
outStream, err := os.Create(prefix + "testblocks.acc")
@ -427,26 +351,21 @@ func TestCreateBasicChain(t *testing.T) {
}
}
// Make a NEO roundtrip (send to myself) and claim GAS.
txNeoRound = transaction.NewContractTX()
txNeoRound.Nonce = getNextNonce()
txNeoRound.ValidUntilBlock = validUntilBlock
txNeoRound.Sender = priv0ScriptHash
txNeoRound.AddInput(&transaction.Input{
PrevHash: txNeo0to1.Hash(),
PrevIndex: 1,
})
txNeoRound.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(),
Amount: neoAmount - util.Fixed8FromInt64(1000),
ScriptHash: priv0.GetScriptHash(),
Position: 0,
})
txNeoRound.Data = new(transaction.ContractTX)
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
require.NoError(t, acc0.SignTx(txNeoRound))
// Prepare some transaction for future submission.
txSendRaw := newNEP5Transfer(neoHash, priv0ScriptHash, priv1.GetScriptHash(), int64(util.Fixed8FromInt64(1000)))
txSendRaw.ValidUntilBlock = validUntilBlock
txSendRaw.Nonce = getNextNonce()
txSendRaw.Sender = priv0ScriptHash
txSendRaw.Cosigners = []transaction.Cosigner{{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
}}
require.NoError(t, addNetworkFee(bc, txSendRaw, acc0))
require.NoError(t, acc0.SignTx(txSendRaw))
bw := io.NewBufBinWriter()
txNeoRound.EncodeBinary(bw.BinWriter)
txSendRaw.EncodeBinary(bw.BinWriter)
t.Logf("sendrawtransaction: %s", hex.EncodeToString(bw.Bytes()))
}
@ -456,7 +375,7 @@ func newNEP5Transfer(sc, from, to util.Uint160, amount int64) *transaction.Trans
emit.Opcode(w.BinWriter, opcode.ASSERT)
script := w.Bytes()
return transaction.NewInvocationTX(script, 0)
return transaction.New(script, 0)
}
func addSender(txs ...*transaction.Transaction) error {

View file

@ -4,20 +4,15 @@ import (
"bytes"
"errors"
"fmt"
"math"
"sort"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
gherr "github.com/pkg/errors"
)
const (
@ -29,15 +24,6 @@ const (
MaxContractParametersNum = 252
// MaxContractStringLen is the maximum length for contract metadata strings.
MaxContractStringLen = 252
// MaxAssetNameLen is the maximum length of asset name.
MaxAssetNameLen = 1024
// MaxAssetPrecision is the maximum precision of asset.
MaxAssetPrecision = 8
// BlocksPerYear is a multiplier for asset renewal.
BlocksPerYear = 2000000
// DefaultAssetLifetime is the default lifetime of an asset (which differs
// from assets created by register tx).
DefaultAssetLifetime = 1 + BlocksPerYear
)
// headerGetVersion returns version from the header.
@ -88,106 +74,6 @@ func txGetAttributes(ic *interop.Context, v *vm.VM) error {
return nil
}
// txGetInputs returns current transaction inputs.
func txGetInputs(ic *interop.Context, v *vm.VM) error {
txInterface := v.Estack().Pop().Value()
tx, ok := txInterface.(*transaction.Transaction)
if !ok {
return errors.New("value is not a transaction")
}
if len(tx.Inputs) > vm.MaxArraySize {
return errors.New("too many inputs")
}
inputs := make([]vm.StackItem, 0, len(tx.Inputs))
for i := range tx.Inputs {
inputs = append(inputs, vm.NewInteropItem(&tx.Inputs[i]))
}
v.Estack().PushVal(inputs)
return nil
}
// txGetOutputs returns current transaction outputs.
func txGetOutputs(ic *interop.Context, v *vm.VM) error {
txInterface := v.Estack().Pop().Value()
tx, ok := txInterface.(*transaction.Transaction)
if !ok {
return errors.New("value is not a transaction")
}
if len(tx.Outputs) > vm.MaxArraySize {
return errors.New("too many outputs")
}
outputs := make([]vm.StackItem, 0, len(tx.Outputs))
for i := range tx.Outputs {
outputs = append(outputs, vm.NewInteropItem(&tx.Outputs[i]))
}
v.Estack().PushVal(outputs)
return nil
}
// txGetReferences returns current transaction references.
func txGetReferences(ic *interop.Context, v *vm.VM) error {
txInterface := v.Estack().Pop().Value()
tx, ok := txInterface.(*transaction.Transaction)
if !ok {
return fmt.Errorf("type mismatch: %T is not a Transaction", txInterface)
}
refs, err := ic.Chain.References(tx)
if err != nil {
return err
}
if len(refs) > vm.MaxArraySize {
return errors.New("too many references")
}
stackrefs := make([]vm.StackItem, 0, len(refs))
for i := range tx.Inputs {
for j := range refs {
if refs[j].In == tx.Inputs[i] {
stackrefs = append(stackrefs, vm.NewInteropItem(refs[j]))
break
}
}
}
v.Estack().PushVal(stackrefs)
return nil
}
// txGetType returns current transaction type.
func txGetType(ic *interop.Context, v *vm.VM) error {
txInterface := v.Estack().Pop().Value()
tx, ok := txInterface.(*transaction.Transaction)
if !ok {
return errors.New("value is not a transaction")
}
v.Estack().PushVal(int(tx.Type))
return nil
}
// txGetUnspentCoins returns current transaction unspent coins.
func txGetUnspentCoins(ic *interop.Context, v *vm.VM) error {
txInterface := v.Estack().Pop().Value()
tx, ok := txInterface.(*transaction.Transaction)
if !ok {
return errors.New("value is not a transaction")
}
ucs, err := ic.DAO.GetUnspentCoinState(tx.Hash())
if err == storage.ErrKeyNotFound {
v.Estack().PushVal([]vm.StackItem{})
return nil
} else if err != nil {
return errors.New("no unspent coin state found")
}
items := make([]vm.StackItem, 0, len(ucs.States))
for i := range items {
if ucs.States[i].State&state.CoinSpent == 0 {
items = append(items, vm.NewInteropItem(&ucs.States[i].Output))
}
}
v.Estack().PushVal(items)
return nil
}
// txGetWitnesses returns current transaction witnesses.
func txGetWitnesses(ic *interop.Context, v *vm.VM) error {
txInterface := v.Estack().Pop().Value()
@ -206,24 +92,6 @@ func txGetWitnesses(ic *interop.Context, v *vm.VM) error {
return nil
}
// invocationTx_GetScript returns invocation script from the current transaction.
func invocationTxGetScript(ic *interop.Context, v *vm.VM) error {
txInterface := v.Estack().Pop().Value()
tx, ok := txInterface.(*transaction.Transaction)
if !ok {
return errors.New("value is not a transaction")
}
inv, ok := tx.Data.(*transaction.InvocationTX)
if tx.Type != transaction.InvocationType || !ok {
return errors.New("value is not an invocation transaction")
}
// It's important not to share inv.Script slice with the code running in VM.
script := make([]byte, len(inv.Script))
copy(script, inv.Script)
v.Estack().PushVal(script)
return nil
}
// witnessGetVerificationScript returns current witness' script.
func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error {
witInterface := v.Estack().Pop().Value()
@ -238,84 +106,6 @@ func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error {
return nil
}
// popInputFromVM returns transaction.Input from the first estack element.
func popInputFromVM(v *vm.VM) (*transaction.Input, error) {
inInterface := v.Estack().Pop().Value()
input, ok := inInterface.(*transaction.Input)
if !ok {
txio, ok := inInterface.(transaction.InOut)
if !ok {
return nil, fmt.Errorf("type mismatch: %T is not an Input or InOut", inInterface)
}
input = &txio.In
}
return input, nil
}
// inputGetHash returns hash from the given input.
func inputGetHash(ic *interop.Context, v *vm.VM) error {
input, err := popInputFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(input.PrevHash.BytesBE())
return nil
}
// inputGetIndex returns index from the given input.
func inputGetIndex(ic *interop.Context, v *vm.VM) error {
input, err := popInputFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(input.PrevIndex)
return nil
}
// popOutputFromVM returns transaction.Input from the first estack element.
func popOutputFromVM(v *vm.VM) (*transaction.Output, error) {
outInterface := v.Estack().Pop().Value()
output, ok := outInterface.(*transaction.Output)
if !ok {
txio, ok := outInterface.(transaction.InOut)
if !ok {
return nil, fmt.Errorf("type mismatch: %T is not an Output or InOut", outInterface)
}
output = &txio.Out
}
return output, nil
}
// outputGetAssetId returns asset ID from the given output.
func outputGetAssetID(ic *interop.Context, v *vm.VM) error {
output, err := popOutputFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(output.AssetID.BytesBE())
return nil
}
// outputGetScriptHash returns scripthash from the given output.
func outputGetScriptHash(ic *interop.Context, v *vm.VM) error {
output, err := popOutputFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(output.ScriptHash.BytesBE())
return nil
}
// outputGetValue returns value (amount) from the given output.
func outputGetValue(ic *interop.Context, v *vm.VM) error {
output, err := popOutputFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(int64(output.Amount))
return nil
}
// attrGetData returns tx attribute data.
func attrGetData(ic *interop.Context, v *vm.VM) error {
attrInterface := v.Estack().Pop().Value()
@ -353,21 +143,6 @@ func bcGetAccount(ic *interop.Context, v *vm.VM) error {
return nil
}
// bcGetAsset returns an asset.
func bcGetAsset(ic *interop.Context, v *vm.VM) error {
asbytes := v.Estack().Pop().Bytes()
ashash, err := util.Uint256DecodeBytesBE(asbytes)
if err != nil {
return err
}
as, err := ic.DAO.GetAssetState(ashash)
if err != nil {
return errors.New("asset not found")
}
v.Estack().PushVal(vm.NewInteropItem(as))
return nil
}
// accountGetBalance returns balance for a given account.
func accountGetBalance(ic *interop.Context, v *vm.VM) error {
accInterface := v.Estack().Pop().Value()
@ -571,203 +346,6 @@ func contractMigrate(ic *interop.Context, v *vm.VM) error {
return contractDestroy(ic, v)
}
// assetCreate creates an asset.
func assetCreate(ic *interop.Context, v *vm.VM) error {
if ic.Trigger != trigger.Application {
return errors.New("can't create asset when not triggered by an application")
}
atype := transaction.AssetType(v.Estack().Pop().BigInt().Int64())
switch atype {
case transaction.Currency, transaction.Share, transaction.Invoice, transaction.Token:
// ok
default:
return fmt.Errorf("wrong asset type: %x", atype)
}
name := string(v.Estack().Pop().Bytes())
if len(name) > MaxAssetNameLen {
return errors.New("too big name")
}
amount := util.Fixed8(v.Estack().Pop().BigInt().Int64())
if amount == util.Fixed8(0) {
return errors.New("asset amount can't be zero")
}
if amount < -util.Satoshi() {
return errors.New("asset amount can't be negative (except special -Satoshi value")
}
if atype == transaction.Invoice && amount != -util.Satoshi() {
return errors.New("invoice assets can only have -Satoshi amount")
}
precision := byte(v.Estack().Pop().BigInt().Int64())
if precision > MaxAssetPrecision {
return fmt.Errorf("can't have asset precision of more than %d", MaxAssetPrecision)
}
if atype == transaction.Share && precision != 0 {
return errors.New("share assets can only have zero precision")
}
if amount != -util.Satoshi() && (int64(amount)%int64(math.Pow10(int(MaxAssetPrecision-precision))) != 0) {
return errors.New("given asset amount has fractional component")
}
owner, err := keys.NewPublicKeyFromBytes(v.Estack().Pop().Bytes())
if err != nil {
return gherr.Wrap(err, "failed to get owner key")
}
if owner.IsInfinity() {
return errors.New("can't have infinity as an owner key")
}
witnessOk, err := runtime.CheckKeyedWitness(ic, v, owner)
if err != nil {
return err
}
if !witnessOk {
return errors.New("witness check didn't succeed")
}
admin, err := util.Uint160DecodeBytesBE(v.Estack().Pop().Bytes())
if err != nil {
return gherr.Wrap(err, "failed to get admin")
}
issuer, err := util.Uint160DecodeBytesBE(v.Estack().Pop().Bytes())
if err != nil {
return gherr.Wrap(err, "failed to get issuer")
}
asset := &state.Asset{
ID: ic.Tx.Hash(),
AssetType: atype,
Name: name,
Amount: amount,
Precision: precision,
Owner: *owner,
Admin: admin,
Issuer: issuer,
Expiration: ic.Chain.BlockHeight() + DefaultAssetLifetime,
}
err = ic.DAO.PutAssetState(asset)
if err != nil {
return gherr.Wrap(err, "failed to Store asset")
}
v.Estack().PushVal(vm.NewInteropItem(asset))
return nil
}
// assetGetAdmin returns asset admin.
func assetGetAdmin(ic *interop.Context, v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
v.Estack().PushVal(as.Admin.BytesBE())
return nil
}
// assetGetAmount returns the overall amount of asset available.
func assetGetAmount(ic *interop.Context, v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
v.Estack().PushVal(int64(as.Amount))
return nil
}
// assetGetAssetId returns the id of an asset.
func assetGetAssetID(ic *interop.Context, v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
v.Estack().PushVal(as.ID.BytesBE())
return nil
}
// assetGetAssetType returns type of an asset.
func assetGetAssetType(ic *interop.Context, v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
v.Estack().PushVal(int(as.AssetType))
return nil
}
// assetGetAvailable returns available (not yet issued) amount of asset.
func assetGetAvailable(ic *interop.Context, v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
v.Estack().PushVal(int(as.Available))
return nil
}
// assetGetIssuer returns issuer of an asset.
func assetGetIssuer(ic *interop.Context, v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
v.Estack().PushVal(as.Issuer.BytesBE())
return nil
}
// assetGetOwner returns owner of an asset.
func assetGetOwner(ic *interop.Context, v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
v.Estack().PushVal(as.Owner.Bytes())
return nil
}
// assetGetPrecision returns precision used to measure this asset.
func assetGetPrecision(ic *interop.Context, v *vm.VM) error {
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
v.Estack().PushVal(int(as.Precision))
return nil
}
// assetRenew updates asset expiration date.
func assetRenew(ic *interop.Context, v *vm.VM) error {
if ic.Trigger != trigger.Application {
return errors.New("can't create asset when not triggered by an application")
}
asInterface := v.Estack().Pop().Value()
as, ok := asInterface.(*state.Asset)
if !ok {
return fmt.Errorf("%T is not an asset state", as)
}
years := byte(v.Estack().Pop().BigInt().Int64())
// Not sure why C# code regets an asset from the Store, but we also do it.
asset, err := ic.DAO.GetAssetState(as.ID)
if err != nil {
return errors.New("can't renew non-existent asset")
}
if asset.Expiration < ic.Chain.BlockHeight()+1 {
asset.Expiration = ic.Chain.BlockHeight() + 1
}
expiration := uint64(asset.Expiration) + uint64(years)*BlocksPerYear
if expiration > math.MaxUint32 {
expiration = math.MaxUint32
}
asset.Expiration = uint32(expiration)
err = ic.DAO.PutAssetState(asset)
if err != nil {
return gherr.Wrap(err, "failed to Store asset")
}
v.Estack().PushVal(expiration)
return nil
}
// runtimeSerialize serializes top stack item into a ByteArray.
func runtimeSerialize(_ *interop.Context, v *vm.VM) error {
return vm.RuntimeSerialize(v)

View file

@ -25,18 +25,13 @@ import (
)
/* Missing tests:
* TestTxGetReferences
* TestTxGetUnspentCoins
* TestTxGetWitnesses
* TestBcGetAccount
* TestBcGetAsset
* TestAccountGetBalance
* TestAccountIsStandard
* TestCreateContractStateFromVM
* TestContractCreate
* TestContractMigrate
* TestAssetCreate
* TestAssetRenew
* TestRuntimeSerialize
* TestRuntimeDeserialize
*/
@ -192,47 +187,6 @@ func TestTxGetAttributes(t *testing.T) {
require.Equal(t, tx.Attributes[0].Usage, value[0].Value().(*transaction.Attribute).Usage)
}
func TestTxGetInputs(t *testing.T) {
v, tx, context, chain := createVMAndPushTX(t)
defer chain.Close()
err := txGetInputs(context, v)
require.NoError(t, err)
value := v.Estack().Pop().Value().([]vm.StackItem)
require.Equal(t, tx.Inputs[0], *value[0].Value().(*transaction.Input))
}
func TestTxGetOutputs(t *testing.T) {
v, tx, context, chain := createVMAndPushTX(t)
defer chain.Close()
err := txGetOutputs(context, v)
require.NoError(t, err)
value := v.Estack().Pop().Value().([]vm.StackItem)
require.Equal(t, tx.Outputs[0], *value[0].Value().(*transaction.Output))
}
func TestTxGetType(t *testing.T) {
v, tx, context, chain := createVMAndPushTX(t)
defer chain.Close()
err := txGetType(context, v)
require.NoError(t, err)
value := v.Estack().Pop().Value().(*big.Int)
require.Equal(t, big.NewInt(int64(tx.Type)), value)
}
func TestInvocationTxGetScript(t *testing.T) {
v, tx, context, chain := createVMAndPushTX(t)
defer chain.Close()
err := invocationTxGetScript(context, v)
require.NoError(t, err)
value := v.Estack().Pop().Value().([]byte)
inv := tx.Data.(*transaction.InvocationTX)
require.Equal(t, inv.Script, value)
}
func TestWitnessGetVerificationScript(t *testing.T) {
v := vm.New()
script := []byte{byte(opcode.PUSHM1), byte(opcode.RET)}
@ -290,14 +244,14 @@ func TestECDSAVerify(t *testing.T) {
})
t.Run("signed interop item", func(t *testing.T) {
tx := transaction.NewInvocationTX([]byte{0, 1, 2}, 1)
tx := transaction.New([]byte{0, 1, 2}, 1)
msg := tx.GetSignedPart()
sign := priv.Sign(msg)
runCase(t, false, true, sign, priv.PublicKey().Bytes(), vm.NewInteropItem(tx))
})
t.Run("signed script container", func(t *testing.T) {
tx := transaction.NewInvocationTX([]byte{0, 1, 2}, 1)
tx := transaction.New([]byte{0, 1, 2}, 1)
msg := tx.GetSignedPart()
sign := priv.Sign(msg)
ic.Container = tx
@ -325,81 +279,6 @@ func TestECDSAVerify(t *testing.T) {
})
}
func TestPopInputFromVM(t *testing.T) {
v, tx, _, chain := createVMAndTX(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(&tx.Inputs[0]))
input, err := popInputFromVM(v)
require.NoError(t, err)
require.Equal(t, tx.Inputs[0], *input)
}
func TestInputGetHash(t *testing.T) {
v, tx, context, chain := createVMAndTX(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(&tx.Inputs[0]))
err := inputGetHash(context, v)
require.NoError(t, err)
hash := v.Estack().Pop().Value()
require.Equal(t, tx.Inputs[0].PrevHash.BytesBE(), hash)
}
func TestInputGetIndex(t *testing.T) {
v, tx, context, chain := createVMAndTX(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(&tx.Inputs[0]))
err := inputGetIndex(context, v)
require.NoError(t, err)
index := v.Estack().Pop().Value()
require.Equal(t, big.NewInt(int64(tx.Inputs[0].PrevIndex)), index)
}
func TestPopOutputFromVM(t *testing.T) {
v, tx, _, chain := createVMAndTX(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(&tx.Outputs[0]))
output, err := popOutputFromVM(v)
require.NoError(t, err)
require.Equal(t, tx.Outputs[0], *output)
}
func TestOutputGetAssetID(t *testing.T) {
v, tx, context, chain := createVMAndTX(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(&tx.Outputs[0]))
err := outputGetAssetID(context, v)
require.NoError(t, err)
assetID := v.Estack().Pop().Value()
require.Equal(t, tx.Outputs[0].AssetID.BytesBE(), assetID)
}
func TestOutputGetScriptHash(t *testing.T) {
v, tx, context, chain := createVMAndTX(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(&tx.Outputs[0]))
err := outputGetScriptHash(context, v)
require.NoError(t, err)
scriptHash := v.Estack().Pop().Value()
require.Equal(t, tx.Outputs[0].ScriptHash.BytesBE(), scriptHash)
}
func TestOutputGetValue(t *testing.T) {
v, tx, context, chain := createVMAndTX(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(&tx.Outputs[0]))
err := outputGetValue(context, v)
require.NoError(t, err)
amount := v.Estack().Pop().Value()
require.Equal(t, big.NewInt(int64(tx.Outputs[0].Amount)), amount)
}
func TestAttrGetData(t *testing.T) {
v, tx, context, chain := createVMAndTX(t)
defer chain.Close()
@ -455,95 +334,7 @@ func TestContractIsPayable(t *testing.T) {
require.Equal(t, contractState.IsPayable(), isPayable)
}
func TestAssetGetAdmin(t *testing.T) {
v, assetState, context, chain := createVMAndAssetState(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(assetState))
err := assetGetAdmin(context, v)
require.NoError(t, err)
admin := v.Estack().Pop().Value()
require.Equal(t, assetState.Admin.BytesBE(), admin)
}
func TestAssetGetAmount(t *testing.T) {
v, assetState, context, chain := createVMAndAssetState(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(assetState))
err := assetGetAmount(context, v)
require.NoError(t, err)
amount := v.Estack().Pop().Value()
require.Equal(t, big.NewInt(int64(assetState.Amount)), amount)
}
func TestAssetGetAssetID(t *testing.T) {
v, assetState, context, chain := createVMAndAssetState(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(assetState))
err := assetGetAssetID(context, v)
require.NoError(t, err)
assetID := v.Estack().Pop().Value()
require.Equal(t, assetState.ID.BytesBE(), assetID)
}
func TestAssetGetAssetType(t *testing.T) {
v, assetState, context, chain := createVMAndAssetState(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(assetState))
err := assetGetAssetType(context, v)
require.NoError(t, err)
assetType := v.Estack().Pop().Value()
require.Equal(t, big.NewInt(int64(assetState.AssetType)), assetType)
}
func TestAssetGetAvailable(t *testing.T) {
v, assetState, context, chain := createVMAndAssetState(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(assetState))
err := assetGetAvailable(context, v)
require.NoError(t, err)
available := v.Estack().Pop().Value()
require.Equal(t, big.NewInt(int64(assetState.Available)), available)
}
func TestAssetGetIssuer(t *testing.T) {
v, assetState, context, chain := createVMAndAssetState(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(assetState))
err := assetGetIssuer(context, v)
require.NoError(t, err)
issuer := v.Estack().Pop().Value()
require.Equal(t, assetState.Issuer.BytesBE(), issuer)
}
func TestAssetGetOwner(t *testing.T) {
v, assetState, context, chain := createVMAndAssetState(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(assetState))
err := assetGetOwner(context, v)
require.NoError(t, err)
owner := v.Estack().Pop().Value()
require.Equal(t, assetState.Owner.Bytes(), owner)
}
func TestAssetGetPrecision(t *testing.T) {
v, assetState, context, chain := createVMAndAssetState(t)
defer chain.Close()
v.Estack().PushVal(vm.NewInteropItem(assetState))
err := assetGetPrecision(context, v)
require.NoError(t, err)
precision := v.Estack().Pop().Value()
require.Equal(t, big.NewInt(int64(assetState.Precision)), precision)
}
// Helper functions to create VM, InteropContext, TX, Account, Contract, Asset.
// Helper functions to create VM, InteropContext, TX, Account, Contract.
func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
v := vm.New()
@ -560,29 +351,6 @@ func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop
return v, tx, context, chain
}
func createVMAndAssetState(t *testing.T) (*vm.VM, *state.Asset, *interop.Context, *Blockchain) {
v := vm.New()
assetState := &state.Asset{
ID: util.Uint256{},
AssetType: transaction.GoverningToken,
Name: "TestAsset",
Amount: 1,
Available: 2,
Precision: 1,
FeeMode: 1,
FeeAddress: random.Uint160(),
Owner: keys.PublicKey{X: big.NewInt(1), Y: big.NewInt(1)},
Admin: random.Uint160(),
Issuer: random.Uint160(),
Expiration: 10,
IsFrozen: false,
}
chain := newTestChain(t)
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, nil)
return v, assetState, context, chain
}
func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) {
v := vm.New()
contractState := &state.Contract{
@ -617,7 +385,7 @@ func createVMAndAccState(t *testing.T) (*vm.VM, *state.Account, *interop.Context
func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
v := vm.New()
script := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
tx := transaction.NewInvocationTX(script, 0)
tx := transaction.New(script, 0)
bytes := make([]byte, 1)
attributes := append(tx.Attributes, transaction.Attribute{
@ -625,21 +393,7 @@ func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Con
Data: bytes,
})
inputs := append(tx.Inputs, transaction.Input{
PrevHash: random.Uint256(),
PrevIndex: 1,
})
outputs := append(tx.Outputs, transaction.Output{
AssetID: random.Uint256(),
Amount: 10,
ScriptHash: random.Uint160(),
Position: 1,
})
tx.Attributes = attributes
tx.Inputs = inputs
tx.Outputs = outputs
chain := newTestChain(t)
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, tx)
return v, tx, context, chain

View file

@ -105,23 +105,12 @@ var neoInterops = []interop.Function{
{Name: "Neo.Account.GetBalance", Func: accountGetBalance, Price: 1},
{Name: "Neo.Account.GetScriptHash", Func: accountGetScriptHash, Price: 1},
{Name: "Neo.Account.IsStandard", Func: accountIsStandard, Price: 100},
{Name: "Neo.Asset.Create", Func: assetCreate, Price: 0},
{Name: "Neo.Asset.GetAdmin", Func: assetGetAdmin, Price: 1},
{Name: "Neo.Asset.GetAmount", Func: assetGetAmount, Price: 1},
{Name: "Neo.Asset.GetAssetId", Func: assetGetAssetID, Price: 1},
{Name: "Neo.Asset.GetAssetType", Func: assetGetAssetType, Price: 1},
{Name: "Neo.Asset.GetAvailable", Func: assetGetAvailable, Price: 1},
{Name: "Neo.Asset.GetIssuer", Func: assetGetIssuer, Price: 1},
{Name: "Neo.Asset.GetOwner", Func: assetGetOwner, Price: 1},
{Name: "Neo.Asset.GetPrecision", Func: assetGetPrecision, Price: 1},
{Name: "Neo.Asset.Renew", Func: assetRenew, Price: 0},
{Name: "Neo.Attribute.GetData", Func: attrGetData, Price: 1},
{Name: "Neo.Attribute.GetUsage", Func: attrGetUsage, Price: 1},
{Name: "Neo.Block.GetTransaction", Func: blockGetTransaction, Price: 1},
{Name: "Neo.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1},
{Name: "Neo.Block.GetTransactions", Func: blockGetTransactions, Price: 1},
{Name: "Neo.Blockchain.GetAccount", Func: bcGetAccount, Price: 100},
{Name: "Neo.Blockchain.GetAsset", Func: bcGetAsset, Price: 100},
{Name: "Neo.Blockchain.GetBlock", Func: bcGetBlock, Price: 200},
{Name: "Neo.Blockchain.GetContract", Func: bcGetContract, Price: 100},
{Name: "Neo.Blockchain.GetHeader", Func: bcGetHeader, Price: 100},
@ -148,18 +137,12 @@ var neoInterops = []interop.Function{
{Name: "Neo.Header.GetPrevHash", Func: headerGetPrevHash, Price: 1},
{Name: "Neo.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1},
{Name: "Neo.Header.GetVersion", Func: headerGetVersion, Price: 1},
{Name: "Neo.Input.GetHash", Func: inputGetHash, Price: 1},
{Name: "Neo.Input.GetIndex", Func: inputGetIndex, Price: 1},
{Name: "Neo.InvocationTransaction.GetScript", Func: invocationTxGetScript, Price: 1},
{Name: "Neo.Iterator.Concat", Func: iterator.Concat, Price: 1},
{Name: "Neo.Iterator.Create", Func: iterator.Create, Price: 1},
{Name: "Neo.Iterator.Key", Func: iterator.Key, Price: 1},
{Name: "Neo.Iterator.Keys", Func: iterator.Keys, Price: 1},
{Name: "Neo.Iterator.Values", Func: iterator.Values, Price: 1},
{Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 1},
{Name: "Neo.Output.GetAssetId", Func: outputGetAssetID, Price: 1},
{Name: "Neo.Output.GetScriptHash", Func: outputGetScriptHash, Price: 1},
{Name: "Neo.Output.GetValue", Func: outputGetValue, Price: 1},
{Name: "Neo.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 200},
{Name: "Neo.Runtime.Deserialize", Func: runtimeDeserialize, Price: 1},
{Name: "Neo.Runtime.GetTime", Func: runtimeGetTime, Price: 1},
@ -176,11 +159,6 @@ var neoInterops = []interop.Function{
{Name: "Neo.StorageContext.AsReadOnly", Func: storageContextAsReadOnly, Price: 1},
{Name: "Neo.Transaction.GetAttributes", Func: txGetAttributes, Price: 1},
{Name: "Neo.Transaction.GetHash", Func: txGetHash, Price: 1},
{Name: "Neo.Transaction.GetInputs", Func: txGetInputs, Price: 1},
{Name: "Neo.Transaction.GetOutputs", Func: txGetOutputs, Price: 1},
{Name: "Neo.Transaction.GetReferences", Func: txGetReferences, Price: 200},
{Name: "Neo.Transaction.GetType", Func: txGetType, Price: 1},
{Name: "Neo.Transaction.GetUnspentCoins", Func: txGetUnspentCoins, Price: 200},
{Name: "Neo.Transaction.GetWitnesses", Func: txGetWitnesses, Price: 200},
{Name: "Neo.Witness.GetVerificationScript", Func: witnessGetVerificationScript, Price: 100},
@ -191,23 +169,12 @@ var neoInterops = []interop.Function{
// Old compatibility APIs.
{Name: "AntShares.Account.GetBalance", Func: accountGetBalance, Price: 1},
{Name: "AntShares.Account.GetScriptHash", Func: accountGetScriptHash, Price: 1},
{Name: "AntShares.Asset.Create", Func: assetCreate, Price: 0},
{Name: "AntShares.Asset.GetAdmin", Func: assetGetAdmin, Price: 1},
{Name: "AntShares.Asset.GetAmount", Func: assetGetAmount, Price: 1},
{Name: "AntShares.Asset.GetAssetId", Func: assetGetAssetID, Price: 1},
{Name: "AntShares.Asset.GetAssetType", Func: assetGetAssetType, Price: 1},
{Name: "AntShares.Asset.GetAvailable", Func: assetGetAvailable, Price: 1},
{Name: "AntShares.Asset.GetIssuer", Func: assetGetIssuer, Price: 1},
{Name: "AntShares.Asset.GetOwner", Func: assetGetOwner, Price: 1},
{Name: "AntShares.Asset.GetPrecision", Func: assetGetPrecision, Price: 1},
{Name: "AntShares.Asset.Renew", Func: assetRenew, Price: 0},
{Name: "AntShares.Attribute.GetData", Func: attrGetData, Price: 1},
{Name: "AntShares.Attribute.GetUsage", Func: attrGetUsage, Price: 1},
{Name: "AntShares.Block.GetTransaction", Func: blockGetTransaction, Price: 1},
{Name: "AntShares.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1},
{Name: "AntShares.Block.GetTransactions", Func: blockGetTransactions, Price: 1},
{Name: "AntShares.Blockchain.GetAccount", Func: bcGetAccount, Price: 100},
{Name: "AntShares.Blockchain.GetAsset", Func: bcGetAsset, Price: 100},
{Name: "AntShares.Blockchain.GetBlock", Func: bcGetBlock, Price: 200},
{Name: "AntShares.Blockchain.GetContract", Func: bcGetContract, Price: 100},
{Name: "AntShares.Blockchain.GetHeader", Func: bcGetHeader, Price: 100},
@ -224,11 +191,6 @@ var neoInterops = []interop.Function{
{Name: "AntShares.Header.GetPrevHash", Func: headerGetPrevHash, Price: 1},
{Name: "AntShares.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1},
{Name: "AntShares.Header.GetVersion", Func: headerGetVersion, Price: 1},
{Name: "AntShares.Input.GetHash", Func: inputGetHash, Price: 1},
{Name: "AntShares.Input.GetIndex", Func: inputGetIndex, Price: 1},
{Name: "AntShares.Output.GetAssetId", Func: outputGetAssetID, Price: 1},
{Name: "AntShares.Output.GetScriptHash", Func: outputGetScriptHash, Price: 1},
{Name: "AntShares.Output.GetValue", Func: outputGetValue, Price: 1},
{Name: "AntShares.Runtime.CheckWitness", Func: runtime.CheckWitness, Price: 200},
{Name: "AntShares.Runtime.Log", Func: runtimeLog, Price: 1},
{Name: "AntShares.Runtime.Notify", Func: runtimeNotify, Price: 1},
@ -238,10 +200,6 @@ var neoInterops = []interop.Function{
{Name: "AntShares.Storage.Put", Func: storagePut, Price: 0},
{Name: "AntShares.Transaction.GetAttributes", Func: txGetAttributes, Price: 1},
{Name: "AntShares.Transaction.GetHash", Func: txGetHash, Price: 1},
{Name: "AntShares.Transaction.GetInputs", Func: txGetInputs, Price: 1},
{Name: "AntShares.Transaction.GetOutputs", Func: txGetOutputs, Price: 1},
{Name: "AntShares.Transaction.GetReferences", Func: txGetReferences, Price: 200},
{Name: "AntShares.Transaction.GetType", Func: txGetType, Price: 1},
}
// initIDinInteropsSlice initializes IDs from names in one given

View file

@ -34,15 +34,6 @@ func TestUnexpectedNonInterops(t *testing.T) {
funcs := []func(*interop.Context, *vm.VM) error{
accountGetBalance,
accountGetScriptHash,
assetGetAdmin,
assetGetAmount,
assetGetAssetID,
assetGetAssetType,
assetGetAvailable,
assetGetIssuer,
assetGetOwner,
assetGetPrecision,
assetRenew,
attrGetData,
attrGetUsage,
blockGetTransaction,
@ -58,12 +49,6 @@ func TestUnexpectedNonInterops(t *testing.T) {
headerGetPrevHash,
headerGetTimestamp,
headerGetVersion,
inputGetHash,
inputGetIndex,
invocationTxGetScript,
outputGetAssetID,
outputGetScriptHash,
outputGetValue,
storageContextAsReadOnly,
storageDelete,
storageFind,
@ -72,11 +57,6 @@ func TestUnexpectedNonInterops(t *testing.T) {
storagePutEx,
txGetAttributes,
txGetHash,
txGetInputs,
txGetOutputs,
txGetReferences,
txGetType,
txGetUnspentCoins,
txGetWitnesses,
witnessGetVerificationScript,
}

View file

@ -12,8 +12,8 @@ import (
var (
// ErrConflict is returned when transaction being added is incompatible
// with the contents of the memory pool (using the same inputs as some
// other transaction in the pool)
// with the contents of the memory pool (Sender doesn't have enough GAS
// to pay for all transactions in the pool).
ErrConflict = errors.New("conflicts with the memory pool")
// ErrDup is returned when transaction being added is already present
// in the memory pool.
@ -33,12 +33,6 @@ type item struct {
// items is a slice of item.
type items []*item
// TxWithFee combines transaction and its precalculated network fee.
type TxWithFee struct {
Tx *transaction.Transaction
Fee util.Fixed8
}
// utilityBalanceAndFees stores sender's balance and overall fees of
// sender's transactions which are currently in mempool
type utilityBalanceAndFees struct {
@ -51,8 +45,6 @@ type Pool struct {
lock sync.RWMutex
verifiedMap map[util.Uint256]*item
verifiedTxes items
inputs []*transaction.Input
claims []*transaction.Input
fees map[util.Uint160]utilityBalanceAndFees
capacity int
@ -79,20 +71,6 @@ func (p *item) CompareTo(otherP *item) int {
return -1
}
if p.isLowPrio && otherP.isLowPrio {
thisIsClaimTx := p.txn.Type == transaction.ClaimType
otherIsClaimTx := otherP.txn.Type == transaction.ClaimType
if thisIsClaimTx != otherIsClaimTx {
// This is a claim Tx and other isn't.
if thisIsClaimTx {
return 1
}
// The other is claim Tx and this isn't.
return -1
}
}
// Fees sorted ascending.
if ret := p.txn.FeePerByte().CompareTo(otherP.txn.FeePerByte()); ret != 0 {
return ret
@ -135,35 +113,6 @@ func (mp *Pool) containsKey(hash util.Uint256) bool {
return false
}
// findIndexForInput finds an index in a sorted Input pointers slice that is
// appropriate to place this input into (or which contains an identical Input).
func findIndexForInput(slice []*transaction.Input, input *transaction.Input) int {
return sort.Search(len(slice), func(n int) bool {
return input.Cmp(slice[n]) <= 0
})
}
// pushInputToSortedSlice pushes new Input into the given slice.
func pushInputToSortedSlice(slice *[]*transaction.Input, input *transaction.Input) {
n := findIndexForInput(*slice, input)
*slice = append(*slice, input)
if n != len(*slice)-1 {
copy((*slice)[n+1:], (*slice)[n:])
(*slice)[n] = input
}
}
// dropInputFromSortedSlice removes given input from the given slice.
func dropInputFromSortedSlice(slice *[]*transaction.Input, input *transaction.Input) {
n := findIndexForInput(*slice, input)
if n == len(*slice) || *input != *(*slice)[n] {
// Not present.
return
}
copy((*slice)[n:], (*slice)[n+1:])
*slice = (*slice)[:len(*slice)-1]
}
// tryAddSendersFee tries to add system fee and network fee to the total sender`s fee in mempool
// and returns false if sender has not enough GAS to pay
func (mp *Pool) tryAddSendersFee(tx *transaction.Transaction, feer Feer) bool {
@ -244,18 +193,6 @@ func (mp *Pool) Add(t *transaction.Transaction, fee Feer) error {
}
mp.addSendersFee(pItem.txn)
// For lots of inputs it might be easier to push them all and sort
// afterwards, but that requires benchmarking.
for i := range t.Inputs {
pushInputToSortedSlice(&mp.inputs, &t.Inputs[i])
}
if t.Type == transaction.ClaimType {
claim := t.Data.(*transaction.ClaimTX)
for i := range claim.Claims {
pushInputToSortedSlice(&mp.claims, &claim.Claims[i])
}
}
updateMempoolMetrics(len(mp.verifiedTxes))
mp.lock.Unlock()
return nil
@ -281,15 +218,6 @@ func (mp *Pool) Remove(hash util.Uint256) {
senderFee := mp.fees[it.txn.Sender]
senderFee.feeSum -= it.txn.SystemFee + it.txn.NetworkFee
mp.fees[it.txn.Sender] = senderFee
for i := range it.txn.Inputs {
dropInputFromSortedSlice(&mp.inputs, &it.txn.Inputs[i])
}
if it.txn.Type == transaction.ClaimType {
claim := it.txn.Data.(*transaction.ClaimTX)
for i := range claim.Claims {
dropInputFromSortedSlice(&mp.claims, &claim.Claims[i])
}
}
}
updateMempoolMetrics(len(mp.verifiedTxes))
mp.lock.Unlock()
@ -303,34 +231,15 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
// We can reuse already allocated slice
// because items are iterated one-by-one in increasing order.
newVerifiedTxes := mp.verifiedTxes[:0]
newInputs := mp.inputs[:0]
newClaims := mp.claims[:0]
mp.fees = make(map[util.Uint160]utilityBalanceAndFees) // it'd be nice to reuse existing map, but we can't easily clear it
for _, itm := range mp.verifiedTxes {
if isOK(itm.txn) && mp.tryAddSendersFee(itm.txn, feer) {
newVerifiedTxes = append(newVerifiedTxes, itm)
for i := range itm.txn.Inputs {
newInputs = append(newInputs, &itm.txn.Inputs[i])
}
if itm.txn.Type == transaction.ClaimType {
claim := itm.txn.Data.(*transaction.ClaimTX)
for i := range claim.Claims {
newClaims = append(newClaims, &claim.Claims[i])
}
}
} else {
delete(mp.verifiedMap, itm.txn.Hash())
}
}
sort.Slice(newInputs, func(i, j int) bool {
return newInputs[i].Cmp(newInputs[j]) < 0
})
sort.Slice(newClaims, func(i, j int) bool {
return newClaims[i].Cmp(newClaims[j]) < 0
})
mp.verifiedTxes = newVerifiedTxes
mp.inputs = newInputs
mp.claims = newClaims
mp.lock.Unlock()
}
@ -345,74 +254,39 @@ func NewMemPool(capacity int) Pool {
}
// TryGetValue returns a transaction and its fee if it exists in the memory pool.
func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, util.Fixed8, bool) {
func (mp *Pool) TryGetValue(hash util.Uint256) (*transaction.Transaction, bool) {
mp.lock.RLock()
defer mp.lock.RUnlock()
if pItem, ok := mp.verifiedMap[hash]; ok {
return pItem.txn, pItem.txn.NetworkFee, ok
return pItem.txn, ok
}
return nil, 0, false
return nil, false
}
// GetVerifiedTransactions returns a slice of Input from all the transactions in the memory pool
// whose hash is not included in excludedHashes.
func (mp *Pool) GetVerifiedTransactions() []TxWithFee {
// GetVerifiedTransactions returns a slice of transactions with their fees.
func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
mp.lock.RLock()
defer mp.lock.RUnlock()
var t = make([]TxWithFee, len(mp.verifiedTxes))
var t = make([]*transaction.Transaction, len(mp.verifiedTxes))
for i := range mp.verifiedTxes {
t[i].Tx = mp.verifiedTxes[i].txn
t[i].Fee = mp.verifiedTxes[i].txn.NetworkFee
t[i] = mp.verifiedTxes[i].txn
}
return t
}
// areInputsInPool tries to find inputs in a given sorted pool and returns true
// if it finds any.
func areInputsInPool(inputs []transaction.Input, pool []*transaction.Input) bool {
for i := range inputs {
n := findIndexForInput(pool, &inputs[i])
if n < len(pool) && *pool[n] == inputs[i] {
return true
}
}
return false
}
// checkTxConflicts is an internal unprotected version of Verify.
func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) bool {
if areInputsInPool(tx.Inputs, mp.inputs) {
return false
}
if !mp.checkBalanceAndUpdate(tx, fee) {
return false
}
switch tx.Type {
case transaction.ClaimType:
claim := tx.Data.(*transaction.ClaimTX)
if areInputsInPool(claim.Claims, mp.claims) {
return false
}
case transaction.IssueType:
// It's a hack, because technically we could check for
// available asset amount, but these transactions are so rare
// that no one really cares about this restriction.
for i := range mp.verifiedTxes {
if mp.verifiedTxes[i].txn.Type == transaction.IssueType {
return false
}
}
}
return true
return mp.checkBalanceAndUpdate(tx, fee)
}
// Verify verifies if the inputs of a transaction tx are already used in any other transaction in the memory pool.
// If yes, the transaction tx is not a valid transaction and the function return false.
// If no, the transaction tx is a valid transaction and the function return true.
// Verify checks if a Sender of tx is able to pay for it (and all the other
// transactions in the pool). If yes, the transaction tx is a valid
// transaction and the function returns true. If no, the transaction tx is
// considered to be invalid the function returns false.
func (mp *Pool) Verify(tx *transaction.Transaction, feer Feer) bool {
mp.lock.RLock()
defer mp.lock.RUnlock()

View file

@ -5,8 +5,8 @@ import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -30,18 +30,18 @@ func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) util.Fixed8 {
func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
mp := NewMemPool(10)
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = 0
_, _, ok := mp.TryGetValue(tx.Hash())
_, ok := mp.TryGetValue(tx.Hash())
require.Equal(t, false, ok)
require.NoError(t, mp.Add(tx, fs))
// Re-adding should fail.
require.Error(t, mp.Add(tx, fs))
tx2, _, ok := mp.TryGetValue(tx.Hash())
tx2, ok := mp.TryGetValue(tx.Hash())
require.Equal(t, true, ok)
require.Equal(t, tx, tx2)
mp.Remove(tx.Hash())
_, _, ok = mp.TryGetValue(tx.Hash())
_, ok = mp.TryGetValue(tx.Hash())
require.Equal(t, false, ok)
// Make sure nothing left in the mempool after removal.
assert.Equal(t, 0, len(mp.verifiedMap))
@ -55,162 +55,13 @@ func TestMemPoolAddRemove(t *testing.T) {
t.Run("high priority", func(t *testing.T) { testMemPoolAddRemoveWithFeer(t, fs) })
}
func TestMemPoolAddRemoveWithInputsAndClaims(t *testing.T) {
mp := NewMemPool(50)
hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25")
require.NoError(t, err)
hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9")
require.NoError(t, err)
mpLessInputs := func(i, j int) bool {
return mp.inputs[i].Cmp(mp.inputs[j]) < 0
}
mpLessClaims := func(i, j int) bool {
return mp.claims[i].Cmp(mp.claims[j]) < 0
}
txm1 := transaction.NewContractTX()
txm1.Nonce = 1
txc1, claim1 := newClaimTX()
for i := 0; i < 5; i++ {
txm1.Inputs = append(txm1.Inputs, transaction.Input{PrevHash: hash1, PrevIndex: uint16(100 - i)})
claim1.Claims = append(claim1.Claims, transaction.Input{PrevHash: hash1, PrevIndex: uint16(100 - i)})
}
require.NoError(t, mp.Add(txm1, &FeerStub{}))
require.NoError(t, mp.Add(txc1, &FeerStub{}))
// Look inside.
assert.Equal(t, len(txm1.Inputs), len(mp.inputs))
assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs))
assert.Equal(t, len(claim1.Claims), len(mp.claims))
assert.True(t, sort.SliceIsSorted(mp.claims, mpLessClaims))
txm2 := transaction.NewContractTX()
txm2.Nonce = 1
txc2, claim2 := newClaimTX()
for i := 0; i < 10; i++ {
txm2.Inputs = append(txm2.Inputs, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i)})
claim2.Claims = append(claim2.Claims, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i)})
}
require.NoError(t, mp.Add(txm2, &FeerStub{}))
require.NoError(t, mp.Add(txc2, &FeerStub{}))
assert.Equal(t, len(txm1.Inputs)+len(txm2.Inputs), len(mp.inputs))
assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs))
assert.Equal(t, len(claim1.Claims)+len(claim2.Claims), len(mp.claims))
assert.True(t, sort.SliceIsSorted(mp.claims, mpLessClaims))
mp.Remove(txm1.Hash())
mp.Remove(txc2.Hash())
assert.Equal(t, len(txm2.Inputs), len(mp.inputs))
assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs))
assert.Equal(t, len(claim1.Claims), len(mp.claims))
assert.True(t, sort.SliceIsSorted(mp.claims, mpLessClaims))
require.NoError(t, mp.Add(txm1, &FeerStub{}))
require.NoError(t, mp.Add(txc2, &FeerStub{}))
assert.Equal(t, len(txm1.Inputs)+len(txm2.Inputs), len(mp.inputs))
assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs))
assert.Equal(t, len(claim1.Claims)+len(claim2.Claims), len(mp.claims))
assert.True(t, sort.SliceIsSorted(mp.claims, mpLessClaims))
mp.RemoveStale(func(t *transaction.Transaction) bool {
if t.Hash() == txc1.Hash() || t.Hash() == txm2.Hash() {
return false
}
return true
}, &FeerStub{})
assert.Equal(t, len(txm1.Inputs), len(mp.inputs))
assert.True(t, sort.SliceIsSorted(mp.inputs, mpLessInputs))
assert.Equal(t, len(claim2.Claims), len(mp.claims))
assert.True(t, sort.SliceIsSorted(mp.claims, mpLessClaims))
}
func TestMemPoolVerifyInputs(t *testing.T) {
mp := NewMemPool(10)
tx := transaction.NewContractTX()
tx.Nonce = 1
inhash1 := random.Uint256()
tx.Inputs = append(tx.Inputs, transaction.Input{PrevHash: inhash1, PrevIndex: 0})
require.Equal(t, true, mp.Verify(tx, &FeerStub{}))
require.NoError(t, mp.Add(tx, &FeerStub{}))
tx2 := transaction.NewContractTX()
tx2.Nonce = 2
inhash2 := random.Uint256()
tx2.Inputs = append(tx2.Inputs, transaction.Input{PrevHash: inhash2, PrevIndex: 0})
require.Equal(t, true, mp.Verify(tx2, &FeerStub{}))
require.NoError(t, mp.Add(tx2, &FeerStub{}))
tx3 := transaction.NewContractTX()
tx3.Nonce = 3
// Different index number, but the same PrevHash as in tx1.
tx3.Inputs = append(tx3.Inputs, transaction.Input{PrevHash: inhash1, PrevIndex: 1})
require.Equal(t, true, mp.Verify(tx3, &FeerStub{}))
// The same input as in tx2.
tx3.Inputs = append(tx3.Inputs, transaction.Input{PrevHash: inhash2, PrevIndex: 0})
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
require.Error(t, mp.Add(tx3, &FeerStub{}))
}
func TestMemPoolVerifyClaims(t *testing.T) {
mp := NewMemPool(50)
tx1, claim1 := newClaimTX()
hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25")
require.NoError(t, err)
hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9")
require.NoError(t, err)
for i := 0; i < 10; i++ {
claim1.Claims = append(claim1.Claims, transaction.Input{PrevHash: hash1, PrevIndex: uint16(i)})
claim1.Claims = append(claim1.Claims, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i)})
}
require.Equal(t, true, mp.Verify(tx1, &FeerStub{}))
require.NoError(t, mp.Add(tx1, &FeerStub{}))
tx2, claim2 := newClaimTX()
for i := 0; i < 10; i++ {
claim2.Claims = append(claim2.Claims, transaction.Input{PrevHash: hash2, PrevIndex: uint16(i + 10)})
}
require.Equal(t, true, mp.Verify(tx2, &FeerStub{}))
require.NoError(t, mp.Add(tx2, &FeerStub{}))
tx3, claim3 := newClaimTX()
claim3.Claims = append(claim3.Claims, transaction.Input{PrevHash: hash1, PrevIndex: 0})
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))
require.Error(t, mp.Add(tx3, &FeerStub{}))
}
func TestMemPoolVerifyIssue(t *testing.T) {
mp := NewMemPool(50)
tx1 := newIssueTX()
require.Equal(t, true, mp.Verify(tx1, &FeerStub{}))
require.NoError(t, mp.Add(tx1, &FeerStub{}))
tx2 := newIssueTX()
require.Equal(t, false, mp.Verify(tx2, &FeerStub{}))
require.Error(t, mp.Add(tx2, &FeerStub{}))
}
func newIssueTX() *transaction.Transaction {
tx := transaction.NewIssueTX()
tx.Outputs = []transaction.Output{
{
AssetID: random.Uint256(),
Amount: util.Fixed8FromInt64(42),
ScriptHash: random.Uint160(),
},
}
return tx
}
func newClaimTX() (*transaction.Transaction, *transaction.ClaimTX) {
cl := &transaction.ClaimTX{}
return transaction.NewClaimTX(cl), cl
}
func TestOverCapacity(t *testing.T) {
var fs = &FeerStub{lowPriority: true}
const mempoolSize = 10
mp := NewMemPool(mempoolSize)
for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = uint32(i)
require.NoError(t, mp.Add(tx, fs))
}
@ -218,19 +69,9 @@ func TestOverCapacity(t *testing.T) {
require.Equal(t, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
// Claim TX has more priority than ordinary lowprio, so it should easily
// fit into the pool.
claim := &transaction.Transaction{
Type: transaction.ClaimType,
Data: &transaction.ClaimTX{},
}
require.NoError(t, mp.Add(claim, fs))
require.Equal(t, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
// Fees are also prioritized.
for i := 0; i < mempoolSize-1; i++ {
tx := transaction.NewContractTX()
for i := 0; i < mempoolSize; i++ {
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Attributes = append(tx.Attributes, transaction.Attribute{
Usage: transaction.Hash1,
Data: util.Uint256{1, 2, 3, 4}.BytesBE(),
@ -244,23 +85,20 @@ func TestOverCapacity(t *testing.T) {
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
}
// Less prioritized txes are not allowed anymore.
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Attributes = append(tx.Attributes, transaction.Attribute{
Usage: transaction.Hash1,
Data: util.Uint256{1, 2, 3, 4}.BytesBE(),
})
tx.NetworkFee = util.Fixed8FromFloat(0.00001)
tx.NetworkFee = util.Fixed8FromFloat(0.000001)
tx.Nonce = txcnt
txcnt++
require.Error(t, mp.Add(tx, fs))
require.Equal(t, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
// But claim tx should still be there.
require.True(t, mp.ContainsKey(claim.Hash()))
// Low net fee, but higher per-byte fee is still a better combination.
tx = transaction.NewContractTX()
tx = transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = txcnt
tx.NetworkFee = util.Fixed8FromFloat(0.00007)
txcnt++
@ -273,7 +111,7 @@ func TestOverCapacity(t *testing.T) {
// High priority always wins over low priority.
fs.lowPriority = false
for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = txcnt
txcnt++
require.NoError(t, mp.Add(tx, fs))
@ -282,7 +120,7 @@ func TestOverCapacity(t *testing.T) {
}
// Good luck with low priority now.
fs.lowPriority = true
tx = transaction.NewContractTX()
tx = transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = txcnt
require.Error(t, mp.Add(tx, fs))
require.Equal(t, mempoolSize, mp.Count())
@ -296,7 +134,7 @@ func TestGetVerified(t *testing.T) {
txes := make([]*transaction.Transaction, 0, mempoolSize)
for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = uint32(i)
txes = append(txes, tx)
require.NoError(t, mp.Add(tx, fs))
@ -304,9 +142,7 @@ func TestGetVerified(t *testing.T) {
require.Equal(t, mempoolSize, mp.Count())
verTxes := mp.GetVerifiedTransactions()
require.Equal(t, mempoolSize, len(verTxes))
for _, txf := range verTxes {
require.Contains(t, txes, txf.Tx)
}
require.ElementsMatch(t, txes, verTxes)
for _, tx := range txes {
mp.Remove(tx.Hash())
}
@ -322,7 +158,7 @@ func TestRemoveStale(t *testing.T) {
txes1 := make([]*transaction.Transaction, 0, mempoolSize/2)
txes2 := make([]*transaction.Transaction, 0, mempoolSize/2)
for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = uint32(i)
if i%2 == 0 {
txes1 = append(txes1, tx)
@ -343,15 +179,15 @@ func TestRemoveStale(t *testing.T) {
require.Equal(t, mempoolSize/2, mp.Count())
verTxes := mp.GetVerifiedTransactions()
for _, txf := range verTxes {
require.NotContains(t, txes1, txf.Tx)
require.Contains(t, txes2, txf.Tx)
require.NotContains(t, txes1, txf)
require.Contains(t, txes2, txf)
}
}
func TestMemPoolFees(t *testing.T) {
mp := NewMemPool(10)
sender0 := util.Uint160{1, 2, 3}
tx0 := transaction.NewContractTX()
tx0 := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx0.NetworkFee = util.Fixed8FromInt64(11000)
tx0.Sender = sender0
// insufficient funds to add transaction, but balance should be stored
@ -364,7 +200,7 @@ func TestMemPoolFees(t *testing.T) {
}, mp.fees[sender0])
// no problems with adding another transaction with lower fee
tx1 := transaction.NewContractTX()
tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx1.NetworkFee = util.Fixed8FromInt64(7000)
tx1.Sender = sender0
require.NoError(t, mp.Add(tx1, &FeerStub{}))
@ -375,7 +211,7 @@ func TestMemPoolFees(t *testing.T) {
}, mp.fees[sender0])
// balance shouldn't change after adding one more transaction
tx2 := transaction.NewContractTX()
tx2 := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx2.NetworkFee = util.Fixed8FromFloat(3000)
tx2.Sender = sender0
require.NoError(t, mp.Add(tx2, &FeerStub{}))
@ -387,7 +223,7 @@ func TestMemPoolFees(t *testing.T) {
}, mp.fees[sender0])
// can't add more transactions as we don't have enough GAS
tx3 := transaction.NewContractTX()
tx3 := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx3.NetworkFee = util.Fixed8FromFloat(0.5)
tx3.Sender = sender0
require.Equal(t, false, mp.Verify(tx3, &FeerStub{}))

View file

@ -10,9 +10,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
)
// GAS represents GAS native contract.
@ -40,11 +38,6 @@ func NewGAS() *GAS {
g.nep5TokenNative = *nep5
desc := newDescriptor("getSysFeeAmount", smartcontract.IntegerType,
manifest.NewParameter("index", smartcontract.IntegerType))
md := newMethodAndPrice(g.getSysFeeAmount, 1, smartcontract.NoneFlag)
g.AddMethod(md, desc, true)
return g
}
@ -98,16 +91,6 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
return nil
}
func (g *GAS) getSysFeeAmount(ic *interop.Context, args []vm.StackItem) vm.StackItem {
index := toBigInt(args[0])
h := ic.Chain.GetHeaderHash(int(index.Int64()))
_, sf, err := ic.DAO.GetBlock(h)
if err != nil {
panic(err)
}
return vm.NewBigIntegerItem(big.NewInt(int64(sf)))
}
func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, []*keys.PublicKey, error) {
vs, err := ic.Chain.GetStandByValidators()
if err != nil {

View file

@ -188,12 +188,9 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
if ic.Block == nil || ic.Block.Index == 0 {
return nil
}
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.Balance.Int64()), acc.BalanceHeight, ic.Block.Index)
if err != nil {
return err
}
gen := ic.Chain.CalculateClaimable(acc.Balance.Int64(), acc.BalanceHeight, ic.Block.Index)
acc.BalanceHeight = ic.Block.Index
n.GAS.mint(ic, h, big.NewInt(int64(sys+net)))
n.GAS.mint(ic, h, big.NewInt(int64(gen)))
return nil
}
@ -206,11 +203,8 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []vm.StackItem) vm.StackIte
}
tr := bs.Trackers[n.Hash]
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(tr.Balance), tr.LastUpdatedBlock, end)
if err != nil {
panic(err)
}
return vm.NewBigIntegerItem(big.NewInt(int64(sys.Add(net))))
gen := ic.Chain.CalculateClaimable(tr.Balance, tr.LastUpdatedBlock, end)
return vm.NewBigIntegerItem(big.NewInt(int64(gen)))
}
func (n *NEO) registerValidator(ic *interop.Context, args []vm.StackItem) vm.StackItem {

View file

@ -169,6 +169,8 @@ func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, a
inc := amount
if isEmpty {
inc = big.NewInt(0)
} else {
inc = new(big.Int).Neg(inc)
}
if err := c.incBalance(ic, from, siFrom, inc); err != nil {
return err
@ -206,11 +208,17 @@ func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []vm.StackItem) vm
}
func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) {
if amount.Sign() == 0 {
return
}
c.addTokens(ic, h, amount)
c.emitTransfer(ic, nil, &h, amount)
}
func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) {
if amount.Sign() == 0 {
return
}
amount = new(big.Int).Neg(amount)
c.addTokens(ic, h, amount)
c.emitTransfer(ic, &h, nil, amount)

View file

@ -92,7 +92,7 @@ func TestNativeContract_Invoke(t *testing.T) {
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "sum", int64(14), int64(28))
script := w.Bytes()
tx := transaction.NewInvocationTX(script, 0)
tx := transaction.New(script, 0)
validUntil := chain.blockHeight + 1
tx.ValidUntilBlock = validUntil
require.NoError(t, addSender(tx))

View file

@ -1,10 +0,0 @@
package core
import "github.com/nspcc-dev/neo-go/pkg/core/transaction"
// spentCoin represents the state of a single spent coin output.
type spentCoin struct {
Output *transaction.Output
StartHeight uint32
EndHeight uint32
}

View file

@ -13,16 +13,6 @@ 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
@ -32,7 +22,6 @@ type Account struct {
ScriptHash util.Uint160
IsFrozen bool
Balances map[util.Uint256][]UnspentBalance
Unclaimed UnclaimedBalances
}
// NewAccount returns a new Account object.
@ -42,7 +31,6 @@ func NewAccount(scriptHash util.Uint160) *Account {
ScriptHash: scriptHash,
IsFrozen: false,
Balances: make(map[util.Uint256][]UnspentBalance),
Unclaimed: UnclaimedBalances{Raw: []byte{}},
}
}
@ -64,10 +52,6 @@ func (s *Account) DecodeBinary(br *io.BinReader) {
}
s.Balances[key] = ubs
}
lenBalances = br.ReadVarUint()
s.Unclaimed.Raw = make([]byte, lenBalances*UnclaimedBalanceSize)
br.ReadBytes(s.Unclaimed.Raw)
}
// EncodeBinary encodes Account to the given BinWriter.
@ -84,9 +68,6 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) {
v[i].EncodeBinary(bw)
}
}
bw.WriteVarUint(uint64(s.Unclaimed.Size()))
bw.WriteBytes(s.Unclaimed.Raw)
}
// DecodeBinary implements io.Serializable interface.
@ -103,24 +84,6 @@ 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 {

View file

@ -30,7 +30,6 @@ func TestDecodeEncodeAccountState(t *testing.T) {
ScriptHash: random.Uint160(),
IsFrozen: true,
Balances: balances,
Unclaimed: UnclaimedBalances{Raw: []byte{}},
}
testserdes.EncodeDecodeBinary(t, a, new(Account))

View file

@ -1,78 +0,0 @@
package state
import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
const feeMode = 0x0
// Asset represents the state of an NEO registered Asset.
type Asset struct {
ID util.Uint256
AssetType transaction.AssetType
Name string
Amount util.Fixed8
Available util.Fixed8
Precision uint8
FeeMode uint8
FeeAddress util.Uint160
Owner keys.PublicKey
Admin util.Uint160
Issuer util.Uint160
Expiration uint32
IsFrozen bool
}
// DecodeBinary implements Serializable interface.
func (a *Asset) DecodeBinary(br *io.BinReader) {
br.ReadBytes(a.ID[:])
a.AssetType = transaction.AssetType(br.ReadB())
a.Name = br.ReadString()
a.Amount.DecodeBinary(br)
a.Available.DecodeBinary(br)
a.Precision = uint8(br.ReadB())
a.FeeMode = uint8(br.ReadB())
a.FeeAddress.DecodeBinary(br)
a.Owner.DecodeBinary(br)
a.Admin.DecodeBinary(br)
a.Issuer.DecodeBinary(br)
a.Expiration = br.ReadU32LE()
a.IsFrozen = br.ReadBool()
}
// EncodeBinary implements Serializable interface.
func (a *Asset) EncodeBinary(bw *io.BinWriter) {
bw.WriteBytes(a.ID[:])
bw.WriteB(byte(a.AssetType))
bw.WriteString(a.Name)
a.Amount.EncodeBinary(bw)
a.Available.EncodeBinary(bw)
bw.WriteB(byte(a.Precision))
bw.WriteB(byte(a.FeeMode))
a.FeeAddress.EncodeBinary(bw)
a.Owner.EncodeBinary(bw)
a.Admin.EncodeBinary(bw)
a.Issuer.EncodeBinary(bw)
bw.WriteU32LE(a.Expiration)
bw.WriteBool(a.IsFrozen)
}
// GetName returns the asset name based on its type.
func (a *Asset) GetName() string {
if a.AssetType == transaction.GoverningToken {
return "NEO"
} else if a.AssetType == transaction.UtilityToken {
return "NEOGas"
}
return a.Name
}

View file

@ -1,41 +0,0 @@
package state
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
)
func TestEncodeDecodeAssetState(t *testing.T) {
asset := &Asset{
ID: random.Uint256(),
AssetType: transaction.Token,
Name: "super cool token",
Amount: util.Fixed8(1000000),
Available: util.Fixed8(100),
Precision: 0,
FeeMode: feeMode,
Owner: keys.PublicKey{},
Admin: random.Uint160(),
Issuer: random.Uint160(),
Expiration: 10,
IsFrozen: false,
}
testserdes.EncodeDecodeBinary(t, asset, new(Asset))
}
func TestAssetState_GetName_NEO(t *testing.T) {
asset := &Asset{AssetType: transaction.GoverningToken}
assert.Equal(t, "NEO", asset.GetName())
}
func TestAssetState_GetName_NEOGas(t *testing.T) {
asset := &Asset{AssetType: transaction.UtilityToken}
assert.Equal(t, "NEOGas", asset.GetName())
}

View file

@ -1,69 +0,0 @@
package state
import (
"bytes"
"encoding/binary"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// UnclaimedBalanceSize is a size of the UnclaimedBalance struct in bytes.
const UnclaimedBalanceSize = util.Uint256Size + 2 + 4 + 4 + 8
// UnclaimedBalances is a slice of UnclaimedBalance.
type UnclaimedBalances struct {
Raw []byte
}
// Size returns an amount of store unclaimed balances.
func (bs *UnclaimedBalances) Size() int {
return len(bs.Raw) / UnclaimedBalanceSize
}
// ForEach iterates over all unclaimed balances.
func (bs *UnclaimedBalances) ForEach(f func(*UnclaimedBalance) error) error {
b := new(UnclaimedBalance)
for i := 0; i < len(bs.Raw); i += UnclaimedBalanceSize {
r := io.NewBinReaderFromBuf(bs.Raw[i : i+UnclaimedBalanceSize])
b.DecodeBinary(r)
if r.Err != nil {
return r.Err
} else if err := f(b); err != nil {
return err
}
}
return nil
}
// Remove removes specified unclaim from the list and returns
// false if it wasn't found.
func (bs *UnclaimedBalances) Remove(tx util.Uint256, index uint16) bool {
const keySize = util.Uint256Size + 2
key := make([]byte, keySize)
copy(key, tx[:])
binary.LittleEndian.PutUint16(key[util.Uint256Size:], index)
for i := 0; i < len(bs.Raw); i += UnclaimedBalanceSize {
if bytes.Equal(bs.Raw[i:i+keySize], key) {
lastIndex := len(bs.Raw) - UnclaimedBalanceSize
if i != lastIndex {
copy(bs.Raw[i:i+UnclaimedBalanceSize], bs.Raw[lastIndex:])
}
bs.Raw = bs.Raw[:lastIndex]
return true
}
}
return false
}
// Put puts new unclaim in a list.
func (bs *UnclaimedBalances) Put(b *UnclaimedBalance) error {
w := io.NewBufBinWriter()
b.EncodeBinary(w.BinWriter)
if w.Err != nil {
return w.Err
}
bs.Raw = append(bs.Raw, w.Bytes()...)
return nil
}

View file

@ -1,73 +0,0 @@
package state
import (
"encoding/binary"
"math/rand"
"testing"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestUnclaimedBalance_Structure(t *testing.T) {
b := randomUnclaimed(t)
buf, err := testserdes.EncodeBinary(b)
require.NoError(t, err)
require.Equal(t, UnclaimedBalanceSize, len(buf))
require.Equal(t, b.Tx.BytesBE(), buf[:util.Uint256Size])
require.Equal(t, b.Index, binary.LittleEndian.Uint16(buf[util.Uint256Size:]))
}
func TestUnclaimedBalances_Put(t *testing.T) {
bs := new(UnclaimedBalances)
b1 := randomUnclaimed(t)
b2 := randomUnclaimed(t)
b3 := randomUnclaimed(t)
require.NoError(t, bs.Put(b1))
require.Equal(t, 1, bs.Size())
require.NoError(t, bs.Put(b2))
require.Equal(t, 2, bs.Size())
require.NoError(t, bs.Put(b3))
require.Equal(t, 3, bs.Size())
require.True(t, bs.Remove(b2.Tx, b2.Index))
require.Equal(t, 2, bs.Size())
require.False(t, bs.Remove(b2.Tx, b2.Index))
require.Equal(t, 2, bs.Size())
require.True(t, bs.Remove(b1.Tx, b1.Index))
require.Equal(t, 1, bs.Size())
require.True(t, bs.Remove(b3.Tx, b3.Index))
require.Equal(t, 0, bs.Size())
}
func TestUnclaimedBalances_ForEach(t *testing.T) {
bs := new(UnclaimedBalances)
b1 := randomUnclaimed(t)
b2 := randomUnclaimed(t)
b3 := randomUnclaimed(t)
require.NoError(t, bs.Put(b1))
require.NoError(t, bs.Put(b2))
require.NoError(t, bs.Put(b3))
var indices []uint16
err := bs.ForEach(func(b *UnclaimedBalance) error {
indices = append(indices, b.Index)
return nil
})
require.NoError(t, err)
require.Equal(t, []uint16{b1.Index, b2.Index, b3.Index}, indices)
}
func randomUnclaimed(t *testing.T) *UnclaimedBalance {
b := new(UnclaimedBalance)
b.Tx = random.Uint256()
b.Index = uint16(rand.Uint32())
b.Start = rand.Uint32()
b.End = rand.Uint32()
b.Value = util.Fixed8(rand.Int63())
return b
}

View file

@ -1,60 +0,0 @@
package state
import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
)
// UnspentCoin hold the state of a unspent coin.
type UnspentCoin struct {
Height uint32
States []OutputState
}
// OutputState combines transaction output (UTXO) and its state
// (spent/claimed...) along with the height of spend (if it's spent).
type OutputState struct {
transaction.Output
SpendHeight uint32
State Coin
}
// NewUnspentCoin returns a new unspent coin state with N confirmed states.
func NewUnspentCoin(height uint32, tx *transaction.Transaction) *UnspentCoin {
u := &UnspentCoin{
Height: height,
States: make([]OutputState, len(tx.Outputs)),
}
for i := range tx.Outputs {
u.States[i] = OutputState{Output: tx.Outputs[i]}
}
return u
}
// EncodeBinary encodes UnspentCoin to the given BinWriter.
func (s *UnspentCoin) EncodeBinary(bw *io.BinWriter) {
bw.WriteU32LE(s.Height)
bw.WriteArray(s.States)
bw.WriteVarUint(uint64(len(s.States)))
}
// DecodeBinary decodes UnspentCoin from the given BinReader.
func (s *UnspentCoin) DecodeBinary(br *io.BinReader) {
s.Height = br.ReadU32LE()
br.ReadArray(&s.States)
}
// EncodeBinary implements Serializable interface.
func (o *OutputState) EncodeBinary(w *io.BinWriter) {
o.Output.EncodeBinary(w)
w.WriteU32LE(o.SpendHeight)
w.WriteB(byte(o.State))
}
// DecodeBinary implements Serializable interface.
func (o *OutputState) DecodeBinary(r *io.BinReader) {
o.Output.DecodeBinary(r)
o.SpendHeight = r.ReadU32LE()
o.State = Coin(r.ReadB())
}

View file

@ -1,47 +0,0 @@
package state
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
)
func TestDecodeEncodeUnspentCoin(t *testing.T) {
unspent := &UnspentCoin{
Height: 100500,
States: []OutputState{
{
Output: transaction.Output{
AssetID: random.Uint256(),
Amount: util.Fixed8(42),
ScriptHash: random.Uint160(),
},
SpendHeight: 201000,
State: CoinSpent,
},
{
Output: transaction.Output{
AssetID: random.Uint256(),
Amount: util.Fixed8(420),
ScriptHash: random.Uint160(),
},
SpendHeight: 0,
State: CoinConfirmed,
},
{
Output: transaction.Output{
AssetID: random.Uint256(),
Amount: util.Fixed8(4200),
ScriptHash: random.Uint160(),
},
SpendHeight: 111000,
State: CoinSpent & CoinClaimed,
},
},
}
testserdes.EncodeDecodeBinary(t, unspent, new(UnspentCoin))
}

View file

@ -10,10 +10,6 @@ const (
DataBlock KeyPrefix = 0x01
DataTransaction KeyPrefix = 0x02
STAccount KeyPrefix = 0x40
STCoin KeyPrefix = 0x44
STSpentCoin KeyPrefix = 0x45
STValidator KeyPrefix = 0x48
STAsset KeyPrefix = 0x4c
STNotification KeyPrefix = 0x4d
STContract KeyPrefix = 0x50
STStorage KeyPrefix = 0x70

View file

@ -11,9 +11,6 @@ var (
DataBlock,
DataTransaction,
STAccount,
STCoin,
STValidator,
STAsset,
STContract,
STStorage,
IXHeaderHashList,
@ -26,9 +23,6 @@ var (
0x01,
0x02,
0x40,
0x44,
0x48,
0x4c,
0x50,
0x70,
0x80,

View file

@ -1,16 +0,0 @@
package transaction
// AssetType represents a NEO asset type.
type AssetType uint8
// Valid asset types.
const (
CreditFlag AssetType = 0x40
DutyFlag AssetType = 0x80
GoverningToken AssetType = 0x00
UtilityToken AssetType = 0x01
Currency AssetType = 0x08
Share AssetType = DutyFlag | 0x10
Invoice AssetType = DutyFlag | 0x18
Token AssetType = CreditFlag | 0x20
)

View file

@ -1,38 +0,0 @@
package transaction
import (
"math/rand"
"github.com/nspcc-dev/neo-go/pkg/io"
)
// ClaimTX represents a claim transaction.
type ClaimTX struct {
Claims []Input
}
// NewClaimTX creates Transaction of ClaimType type.
func NewClaimTX(claim *ClaimTX) *Transaction {
return &Transaction{
Type: ClaimType,
Version: 0,
Nonce: rand.Uint32(),
Data: claim,
Attributes: []Attribute{},
Cosigners: []Cosigner{},
Inputs: []Input{},
Outputs: []Output{},
Scripts: []Witness{},
Trimmed: false,
}
}
// DecodeBinary implements Serializable interface.
func (tx *ClaimTX) DecodeBinary(br *io.BinReader) {
br.ReadArray(&tx.Claims)
}
// EncodeBinary implements Serializable interface.
func (tx *ClaimTX) EncodeBinary(bw *io.BinWriter) {
bw.WriteArray(tx.Claims)
}

View file

@ -1,35 +0,0 @@
package transaction
import (
"math/rand"
"github.com/nspcc-dev/neo-go/pkg/io"
)
// ContractTX represents a contract transaction.
// This TX has not special attributes.
type ContractTX struct{}
// NewContractTX creates Transaction of ContractType type.
func NewContractTX() *Transaction {
return &Transaction{
Type: ContractType,
Version: 0,
Nonce: rand.Uint32(),
Data: &ContractTX{},
Attributes: []Attribute{},
Cosigners: []Cosigner{},
Inputs: []Input{},
Outputs: []Output{},
Scripts: []Witness{},
Trimmed: false,
}
}
// DecodeBinary implements Serializable interface.
func (tx *ContractTX) DecodeBinary(r *io.BinReader) {
}
// EncodeBinary implements Serializable interface.
func (tx *ContractTX) EncodeBinary(w *io.BinWriter) {
}

View file

@ -1,30 +0,0 @@
package transaction
//TODO NEO3.0: Update binary
/*
func TestEncodeDecodeContract(t *testing.T) {
// mainnet transaction: bdf6cc3b9af12a7565bda80933a75ee8cef1bc771d0d58effc08e4c8b436da79
rawtx := "80000001888da99f8f497fd65c4325786a09511159c279af4e7eb532e9edd628c87cc1ee0000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50082167010000000a8666b4830229d6a1a9b80f6088059191c122d2b0141409e79e132290c82916a88f1a3db5cf9f3248b780cfece938ab0f0812d0e188f3a489c7d1a23def86bd69d863ae67de753b2c2392e9497eadc8eb9fc43aa52c645232103e2f6a334e05002624cf616f01a62cff2844c34a3b08ca16048c259097e315078ac"
tx := decodeTransaction(rawtx, t)
assert.Equal(t, ContractType, tx.Type)
assert.IsType(t, tx.Data, &ContractTX{})
assert.Equal(t, 0, int(tx.Version))
assert.Equal(t, 1, len(tx.Inputs))
input := tx.Inputs[0]
assert.Equal(t, "eec17cc828d6ede932b57e4eaf79c2591151096a7825435cd67f498f9fa98d88", input.PrevHash.StringLE())
assert.Equal(t, 0, int(input.PrevIndex))
assert.Equal(t, int64(706), tx.Outputs[0].Amount.IntegralValue())
assert.Equal(t, int32(0), tx.Outputs[0].Amount.FractionalValue())
assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", tx.Outputs[0].AssetID.StringLE())
assert.Equal(t, "a8666b4830229d6a1a9b80f6088059191c122d2b", tx.Outputs[0].ScriptHash.String())
assert.Equal(t, "bdf6cc3b9af12a7565bda80933a75ee8cef1bc771d0d58effc08e4c8b436da79", tx.Hash().StringLE())
// Encode
data, err := testserdes.EncodeBinary(tx)
assert.NoError(t, err)
assert.Equal(t, rawtx, hex.EncodeToString(data))
}
*/

View file

@ -9,8 +9,6 @@ import (
var (
//TODO NEO3.0: Update binary
// https://neotracker.io/tx/2c6a45547b3898318e400e541628990a07acb00f3b9a15a8e966ae49525304da
rawClaimTX = "020004bc67ba325d6412ff4c55b10f7e9afb54bbb2228d201b37363c3d697ac7c198f70300591cd454d7318d2087c0196abfbbd1573230380672f0f0cd004dcb4857e58cbd010031bcfbed573f5318437e95edd603922a4455ff3326a979fdd1c149a84c4cb0290000b51eb6159c58cac4fe23d90e292ad2bcb7002b0da2c474e81e1889c0649d2c490000000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c603b555f00000000005d9de59d99c0d1f6ed1496444473f4a0b538302f014140456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb232103945fae1ed3c31d778f149192b76734fcc951b400ba3598faa81ff92ebe477eacac"
// https://neotracker.io/tx/fe4b3af60677204c57e573a57bdc97bc5059b05ad85b1474f84431f88d910f64
rawInvocationTX = "d101590400b33f7114839c33710da24cf8e7d536b8d244f3991cf565c8146063795d3b9b3cd55aef026eae992b91063db0db53c1087472616e7366657267c5cc1cb5392019e2cc4e6d6b5ea54c8d4b6d11acf166cb072961424c54f6000000000000000001206063795d3b9b3cd55aef026eae992b91063db0db0000014140c6a131c55ca38995402dff8e92ac55d89cbed4b98dfebbcb01acbc01bd78fa2ce2061be921b8999a9ab79c2958875bccfafe7ce1bbbaf1f56580815ea3a4feed232102d41ddce2c97be4c9aa571b8a32cbc305aa29afffbcae71b0ef568db0e93929aaac"
)

View file

@ -1,8 +0,0 @@
package transaction
// InOut represents an Input bound to its corresponding Output which is a useful
// combination for many purposes.
type InOut struct {
In Input
Out Output
}

View file

@ -1,94 +0,0 @@
package transaction
import (
"sort"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// Input represents a Transaction input (CoinReference).
type Input struct {
// The hash of the previous transaction.
PrevHash util.Uint256 `json:"txid"`
// The index of the previous transaction.
PrevIndex uint16 `json:"vout"`
}
// DecodeBinary implements Serializable interface.
func (in *Input) DecodeBinary(br *io.BinReader) {
br.ReadBytes(in.PrevHash[:])
in.PrevIndex = br.ReadU16LE()
}
// EncodeBinary implements Serializable interface.
func (in *Input) EncodeBinary(bw *io.BinWriter) {
bw.WriteBytes(in.PrevHash[:])
bw.WriteU16LE(in.PrevIndex)
}
// Cmp compares two Inputs by their hash and index allowing to make a set of
// transactions ordered.
func (in *Input) Cmp(other *Input) int {
hashcmp := in.PrevHash.CompareTo(other.PrevHash)
if hashcmp == 0 {
return int(in.PrevIndex) - int(other.PrevIndex)
}
return hashcmp
}
// MapInputsToSorted maps given slice of inputs into a new slice of pointers
// to inputs sorted by their PrevHash and PrevIndex.
func MapInputsToSorted(ins []Input) []*Input {
ptrs := make([]*Input, len(ins))
for i := range ins {
ptrs[i] = &ins[i]
}
sort.Slice(ptrs, func(i, j int) bool {
return ptrs[i].Cmp(ptrs[j]) < 0
})
return ptrs
}
// GroupInputsByPrevHash groups all TX inputs by their previous hash into
// several slices (which actually are subslices of one new slice with pointers).
// Each of these slices contains at least one element.
func GroupInputsByPrevHash(ins []Input) [][]*Input {
if len(ins) == 0 {
return nil
}
ptrs := MapInputsToSorted(ins)
var first int
res := make([][]*Input, 0)
currentHash := ptrs[0].PrevHash
for i := range ptrs {
if !currentHash.Equals(ptrs[i].PrevHash) {
res = append(res, ptrs[first:i])
first = i
currentHash = ptrs[i].PrevHash
}
}
res = append(res, ptrs[first:])
return res
}
// HaveDuplicateInputs checks inputs for duplicates and returns true if there are
// any.
func HaveDuplicateInputs(ins []Input) bool {
if len(ins) < 2 {
return false
}
if len(ins) == 2 {
return ins[0] == ins[1]
}
ptrs := MapInputsToSorted(ins)
for i := 1; i < len(ptrs); i++ {
if *ptrs[i] == *ptrs[i-1] {
return true
}
}
return false
}

View file

@ -1,144 +0,0 @@
package transaction
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGroupInputsByPrevHash0(t *testing.T) {
inputs := make([]Input, 0)
res := GroupInputsByPrevHash(inputs)
require.Equal(t, 0, len(res))
}
func TestGroupInputsByPrevHash1(t *testing.T) {
inputs := make([]Input, 0)
hash, err := util.Uint256DecodeStringLE("46168f963d6d8168a870405f66cc9e13a235791013b8ee2f90cc20a8293bd1af")
require.NoError(t, err)
inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42})
res := GroupInputsByPrevHash(inputs)
require.Equal(t, 1, len(res))
require.Equal(t, 1, len(res[0]))
assert.Equal(t, hash, res[0][0].PrevHash)
assert.Equal(t, uint16(42), res[0][0].PrevIndex)
}
func TestGroupInputsByPrevHashMany(t *testing.T) {
hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25")
require.NoError(t, err)
hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9")
require.NoError(t, err)
hash3, err := util.Uint256DecodeStringBE("caa41245c3e48ddc13dabe989ba8fbc59418e9228fef9efb62855b0b17d7448b")
require.NoError(t, err)
inputs := make([]Input, 0)
for i := 0; i < 10; i++ {
inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: uint16(i)})
inputs = append(inputs, Input{PrevHash: hash2, PrevIndex: uint16(i)})
inputs = append(inputs, Input{PrevHash: hash3, PrevIndex: uint16(i)})
}
for i := 15; i < 20; i++ {
inputs = append(inputs, Input{PrevHash: hash3, PrevIndex: uint16(i)})
}
for i := 10; i < 15; i++ {
inputs = append(inputs, Input{PrevHash: hash2, PrevIndex: uint16(i)})
inputs = append(inputs, Input{PrevHash: hash3, PrevIndex: uint16(i)})
}
seen := make(map[uint16]bool)
res := GroupInputsByPrevHash(inputs)
require.Equal(t, 3, len(res))
assert.Equal(t, hash2, res[0][0].PrevHash)
assert.Equal(t, 15, len(res[0]))
for i := range res[0] {
assert.Equal(t, res[0][i].PrevHash, res[0][0].PrevHash)
assert.Equal(t, false, seen[res[0][i].PrevIndex])
seen[res[0][i].PrevIndex] = true
}
seen = make(map[uint16]bool)
assert.Equal(t, hash1, res[1][0].PrevHash)
assert.Equal(t, 10, len(res[1]))
for i := range res[1] {
assert.Equal(t, res[1][i].PrevHash, res[1][0].PrevHash)
assert.Equal(t, false, seen[res[1][i].PrevIndex])
seen[res[1][i].PrevIndex] = true
}
seen = make(map[uint16]bool)
assert.Equal(t, hash3, res[2][0].PrevHash)
assert.Equal(t, 20, len(res[2]))
for i := range res[2] {
assert.Equal(t, res[2][i].PrevHash, res[2][0].PrevHash)
assert.Equal(t, false, seen[res[2][i].PrevIndex])
seen[res[2][i].PrevIndex] = true
}
}
func TestHaveDuplicateInputs0(t *testing.T) {
inputs := make([]Input, 0)
require.False(t, HaveDuplicateInputs(inputs))
}
func TestHaveDuplicateInputs1(t *testing.T) {
inputs := make([]Input, 0)
hash, err := util.Uint256DecodeStringLE("46168f963d6d8168a870405f66cc9e13a235791013b8ee2f90cc20a8293bd1af")
require.NoError(t, err)
inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42})
require.False(t, HaveDuplicateInputs(inputs))
}
func TestHaveDuplicateInputs2True(t *testing.T) {
inputs := make([]Input, 0)
hash, err := util.Uint256DecodeStringLE("46168f963d6d8168a870405f66cc9e13a235791013b8ee2f90cc20a8293bd1af")
require.NoError(t, err)
inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42})
inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42})
require.True(t, HaveDuplicateInputs(inputs))
}
func TestHaveDuplicateInputs2FalseInd(t *testing.T) {
inputs := make([]Input, 0)
hash, err := util.Uint256DecodeStringLE("46168f963d6d8168a870405f66cc9e13a235791013b8ee2f90cc20a8293bd1af")
require.NoError(t, err)
inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 42})
inputs = append(inputs, Input{PrevHash: hash, PrevIndex: 41})
require.False(t, HaveDuplicateInputs(inputs))
}
func TestHaveDuplicateInputs2FalseHash(t *testing.T) {
inputs := make([]Input, 0)
hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25")
require.NoError(t, err)
hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9")
require.NoError(t, err)
inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: 42})
inputs = append(inputs, Input{PrevHash: hash2, PrevIndex: 42})
require.False(t, HaveDuplicateInputs(inputs))
}
func TestHaveDuplicateInputsMFalse(t *testing.T) {
inputs := make([]Input, 0)
hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25")
require.NoError(t, err)
hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9")
require.NoError(t, err)
for i := 0; i < 10; i++ {
inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: uint16(i)})
inputs = append(inputs, Input{PrevHash: hash2, PrevIndex: uint16(i)})
}
require.False(t, HaveDuplicateInputs(inputs))
}
func TestHaveDuplicateInputsMTrue(t *testing.T) {
inputs := make([]Input, 0)
hash1, err := util.Uint256DecodeStringBE("a83ba6ede918a501558d3170a124324aedc89909e64c4ff2c6f863094f980b25")
require.NoError(t, err)
hash2, err := util.Uint256DecodeStringBE("629397158f852e838077bb2715b13a2e29b0a51c2157e5466321b70ed7904ce9")
require.NoError(t, err)
for i := 0; i < 10; i++ {
inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: uint16(i)})
inputs = append(inputs, Input{PrevHash: hash2, PrevIndex: uint16(i)})
}
inputs = append(inputs, Input{PrevHash: hash1, PrevIndex: 0})
require.True(t, HaveDuplicateInputs(inputs))
}

View file

@ -1,65 +0,0 @@
package transaction
import (
"errors"
"math/rand"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// InvocationTX represents a invocation transaction and is used to
// deploy smart contract to the NEO blockchain.
type InvocationTX struct {
// Script output of the smart contract.
Script []byte
// Gas cost of the smart contract.
Gas util.Fixed8
Version uint8
}
// NewInvocationTX returns a new invocation transaction.
func NewInvocationTX(script []byte, gas util.Fixed8) *Transaction {
return &Transaction{
Type: InvocationType,
Version: 1,
Nonce: rand.Uint32(),
Data: &InvocationTX{
Script: script,
Gas: gas,
Version: 1,
},
Attributes: []Attribute{},
Cosigners: []Cosigner{},
Inputs: []Input{},
Outputs: []Output{},
Scripts: []Witness{},
}
}
// DecodeBinary implements Serializable interface.
func (tx *InvocationTX) DecodeBinary(br *io.BinReader) {
tx.Script = br.ReadVarBytes()
if br.Err == nil && len(tx.Script) == 0 {
br.Err = errors.New("no script")
return
}
if tx.Version >= 1 {
tx.Gas.DecodeBinary(br)
if br.Err == nil && tx.Gas.LessThan(0) {
br.Err = errors.New("negative gas")
return
}
} else {
tx.Gas = util.Fixed8FromInt64(0)
}
}
// EncodeBinary implements Serializable interface.
func (tx *InvocationTX) EncodeBinary(bw *io.BinWriter) {
bw.WriteVarBytes(tx.Script)
if tx.Version >= 1 {
tx.Gas.EncodeBinary(bw)
}
}

View file

@ -1,54 +0,0 @@
package transaction
import (
"encoding/hex"
"testing"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInvocationZeroScript(t *testing.T) {
// Zero-length script.
in, err := hex.DecodeString("000000000000000000")
require.NoError(t, err)
inv := &InvocationTX{Version: 1}
assert.Error(t, testserdes.DecodeBinary(in, inv))
// PUSH1 script.
in, err = hex.DecodeString("01510000000000000000")
require.NoError(t, err)
assert.NoError(t, testserdes.DecodeBinary(in, inv))
}
func TestInvocationNegativeGas(t *testing.T) {
// Negative GAS
in, err := hex.DecodeString("015100000000000000ff")
require.NoError(t, err)
inv := &InvocationTX{Version: 1}
assert.Error(t, testserdes.DecodeBinary(in, inv))
// Positive GAS.
in, err = hex.DecodeString("01510100000000000000")
require.NoError(t, err)
assert.NoError(t, testserdes.DecodeBinary(in, inv))
assert.Equal(t, util.Fixed8(1), inv.Gas)
}
func TestInvocationVersionZero(t *testing.T) {
in, err := hex.DecodeString("0151")
require.NoError(t, err)
inv := &InvocationTX{Version: 1}
assert.Error(t, testserdes.DecodeBinary(in, inv))
inv = &InvocationTX{Version: 0}
assert.NoError(t, testserdes.DecodeBinary(in, inv))
assert.Equal(t, util.Fixed8(0), inv.Gas)
}

View file

@ -1,35 +0,0 @@
package transaction
import (
"math/rand"
"github.com/nspcc-dev/neo-go/pkg/io"
)
// IssueTX represents a issue transaction.
// This TX has not special attributes.
type IssueTX struct{}
// NewIssueTX creates Transaction of IssueType type.
func NewIssueTX() *Transaction {
return &Transaction{
Type: IssueType,
Version: 0,
Nonce: rand.Uint32(),
Data: &IssueTX{},
Attributes: []Attribute{},
Cosigners: []Cosigner{},
Inputs: []Input{},
Outputs: []Output{},
Scripts: []Witness{},
Trimmed: false,
}
}
// DecodeBinary implements Serializable interface.
func (tx *IssueTX) DecodeBinary(r *io.BinReader) {
}
// EncodeBinary implements Serializable interface.
func (tx *IssueTX) EncodeBinary(w *io.BinWriter) {
}

View file

@ -1,82 +0,0 @@
package transaction
import (
"encoding/json"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// Output represents a Transaction output.
type Output struct {
// The NEO asset id used in the transaction.
AssetID util.Uint256 `json:"asset"`
// Amount of AssetType send or received.
Amount util.Fixed8 `json:"value"`
// The address of the recipient.
ScriptHash util.Uint160 `json:"address"`
// The position of the Output in slice []Output. This is actually set in NewTransactionOutputRaw
// and used for displaying purposes.
Position int `json:"n"`
}
type outputAux struct {
AssetID util.Uint256 `json:"asset"`
Amount util.Fixed8 `json:"value"`
ScriptHash string `json:"address"`
Position int `json:"n"`
}
// NewOutput returns a new transaction output.
func NewOutput(assetID util.Uint256, amount util.Fixed8, scriptHash util.Uint160) *Output {
return &Output{
AssetID: assetID,
Amount: amount,
ScriptHash: scriptHash,
}
}
// DecodeBinary implements Serializable interface.
func (out *Output) DecodeBinary(br *io.BinReader) {
br.ReadBytes(out.AssetID[:])
out.Amount.DecodeBinary(br)
br.ReadBytes(out.ScriptHash[:])
}
// EncodeBinary implements Serializable interface.
func (out *Output) EncodeBinary(bw *io.BinWriter) {
bw.WriteBytes(out.AssetID[:])
out.Amount.EncodeBinary(bw)
bw.WriteBytes(out.ScriptHash[:])
}
// MarshalJSON implements the Marshaler interface.
func (out *Output) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"asset": out.AssetID,
"value": out.Amount,
"address": address.Uint160ToString(out.ScriptHash),
"n": out.Position,
})
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (out *Output) UnmarshalJSON(data []byte) error {
var outAux outputAux
err := json.Unmarshal(data, &outAux)
if err != nil {
return err
}
out.ScriptHash, err = address.StringToUint160(outAux.ScriptHash)
if err != nil {
return err
}
out.Amount = outAux.Amount
out.AssetID = outAux.AssetID
out.Position = outAux.Position
return nil
}

View file

@ -1,82 +0,0 @@
package transaction
import (
"encoding/json"
"math/rand"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// RegisterTX represents a register transaction.
// NOTE: This is deprecated.
type RegisterTX struct {
// The type of the asset being registered.
AssetType AssetType
// Name of the asset being registered.
Name string
// Amount registered.
// Unlimited mode -0.00000001.
Amount util.Fixed8
// Decimals.
Precision uint8
// Public key of the owner.
Owner keys.PublicKey
Admin util.Uint160
}
// NewRegisterTX creates Transaction of RegisterType type.
func NewRegisterTX(register *RegisterTX) *Transaction {
return &Transaction{
Type: RegisterType,
Version: 0,
Nonce: rand.Uint32(),
Data: register,
Attributes: []Attribute{},
Cosigners: []Cosigner{},
Inputs: []Input{},
Outputs: []Output{},
Scripts: []Witness{},
Trimmed: false,
}
}
// DecodeBinary implements Serializable interface.
func (tx *RegisterTX) DecodeBinary(br *io.BinReader) {
tx.AssetType = AssetType(br.ReadB())
tx.Name = br.ReadString()
tx.Amount.DecodeBinary(br)
tx.Precision = uint8(br.ReadB())
tx.Owner.DecodeBinary(br)
tx.Admin.DecodeBinary(br)
}
// EncodeBinary implements Serializable interface.
func (tx *RegisterTX) EncodeBinary(bw *io.BinWriter) {
bw.WriteB(byte(tx.AssetType))
bw.WriteString(tx.Name)
tx.Amount.EncodeBinary(bw)
bw.WriteB(byte(tx.Precision))
bw.WriteBytes(tx.Owner.Bytes())
tx.Admin.EncodeBinary(bw)
}
// registeredAsset is a wrapper for RegisterTransaction
type registeredAsset struct {
AssetType AssetType `json:"type,omitempty"`
Name json.RawMessage `json:"name,omitempty"`
Amount util.Fixed8 `json:"amount,omitempty"`
Precision uint8 `json:"precision,omitempty"`
Owner keys.PublicKey `json:"owner,omitempty"`
Admin string `json:"admin,omitempty"`
}

View file

@ -1,46 +0,0 @@
package transaction
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
)
func TestRegisterTX(t *testing.T) {
someuint160, _ := util.Uint160DecodeStringBE("4d3b96ae1bcc5a585e075e3b81920210dec16302")
registerTx := &RegisterTX{
AssetType: UtilityToken,
Name: "this is some token I created",
Amount: util.Fixed8FromInt64(1000000),
Precision: 8,
Admin: someuint160,
}
tx := NewRegisterTX(registerTx)
_ = tx.Hash()
testserdes.EncodeDecodeBinary(t, tx, new(Transaction))
}
//TODO NEO3.0: update binary
/*
func TestDecodeRegisterTXFromRawString(t *testing.T) {
rawTX := "400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000"
b, err := hex.DecodeString(rawTX)
require.NoError(t, err)
tx := &Transaction{}
assert.NoError(t, testserdes.DecodeBinary(b, tx))
assert.Equal(t, RegisterType, tx.Type)
txData := tx.Data.(*RegisterTX)
assert.Equal(t, GoverningToken, txData.AssetType)
assert.Equal(t, "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]", txData.Name)
assert.Equal(t, util.Fixed8FromInt64(100000000), txData.Amount)
assert.Equal(t, uint8(0), txData.Precision)
assert.Equal(t, keys.PublicKey{}, txData.Owner)
assert.Equal(t, "Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt", address.Uint160ToString(txData.Admin))
assert.Equal(t, "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b", tx.Hash().StringLE())
testserdes.EncodeDecodeBinary(t, tx, new(Transaction))
}
*/

View file

@ -1,9 +0,0 @@
package transaction
import "github.com/nspcc-dev/neo-go/pkg/util"
// Result represents the Result of a transaction.
type Result struct {
AssetID util.Uint256
Amount util.Fixed8
}

View file

@ -4,7 +4,7 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/rand"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
@ -26,9 +26,6 @@ const (
// Transaction is a process recorded in the NEO blockchain.
type Transaction struct {
// The type of the transaction.
Type TXType
// The trading version which is currently 0.
Version uint8
@ -48,9 +45,8 @@ type Transaction struct {
// transaction should fail verification.
ValidUntilBlock uint32
// Data specific to the type of the transaction.
// This is always a pointer to a <Type>Transaction.
Data TXer
// Code to run in NeoVM for this transaction.
Script []byte
// Transaction attributes.
Attributes []Attribute
@ -58,12 +54,6 @@ type Transaction struct {
// Transaction cosigners (not include Sender).
Cosigners []Cosigner
// The inputs of the transaction.
Inputs []Input
// The outputs of the transaction.
Outputs []Output
// The scripts that comes with this transaction.
// Scripts exist out of the verification script
// and invocation script.
@ -89,6 +79,20 @@ func NewTrimmedTX(hash util.Uint256) *Transaction {
}
}
// New returns a new transaction to execute given script and pay given system
// fee.
func New(script []byte, gas util.Fixed8) *Transaction {
return &Transaction{
Version: 0,
Nonce: rand.Uint32(),
Script: script,
SystemFee: gas,
Attributes: []Attribute{},
Cosigners: []Cosigner{},
Scripts: []Witness{},
}
}
// Hash returns the hash of the transaction.
func (t *Transaction) Hash() util.Uint256 {
if t.hash.Equals(util.Uint256{}) {
@ -109,20 +113,13 @@ func (t *Transaction) VerificationHash() util.Uint256 {
return t.verificationHash
}
// AddOutput adds the given output to the transaction outputs.
func (t *Transaction) AddOutput(out *Output) {
t.Outputs = append(t.Outputs, *out)
}
// AddInput adds the given input to the transaction inputs.
func (t *Transaction) AddInput(in *Input) {
t.Inputs = append(t.Inputs, *in)
}
// DecodeBinary implements Serializable interface.
func (t *Transaction) DecodeBinary(br *io.BinReader) {
t.Type = TXType(br.ReadB())
t.Version = uint8(br.ReadB())
if t.Version > 0 {
br.Err = errors.New("only version 0 is supported")
return
}
t.Nonce = br.ReadU32LE()
t.Sender.DecodeBinary(br)
t.SystemFee.DecodeBinary(br)
@ -140,7 +137,6 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
return
}
t.ValidUntilBlock = br.ReadU32LE()
t.decodeData(br)
br.ReadArray(&t.Attributes)
@ -154,14 +150,12 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
}
}
br.ReadArray(&t.Inputs)
br.ReadArray(&t.Outputs)
for i := range t.Outputs {
if t.Outputs[i].Amount.LessThan(0) {
br.Err = errors.New("negative output")
return
}
t.Script = br.ReadVarBytes()
if br.Err == nil && len(t.Script) == 0 {
br.Err = errors.New("no script")
return
}
br.ReadArray(&t.Scripts)
// Create the hash of the transaction at decode, so we dont need
@ -171,28 +165,6 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
}
}
func (t *Transaction) decodeData(r *io.BinReader) {
switch t.Type {
case InvocationType:
t.Data = &InvocationTX{Version: t.Version}
t.Data.(*InvocationTX).DecodeBinary(r)
case ClaimType:
t.Data = &ClaimTX{}
t.Data.(*ClaimTX).DecodeBinary(r)
case ContractType:
t.Data = &ContractTX{}
t.Data.(*ContractTX).DecodeBinary(r)
case RegisterType:
t.Data = &RegisterTX{}
t.Data.(*RegisterTX).DecodeBinary(r)
case IssueType:
t.Data = &IssueTX{}
t.Data.(*IssueTX).DecodeBinary(r)
default:
r.Err = fmt.Errorf("invalid TX type %x", t.Type)
}
}
// EncodeBinary implements Serializable interface.
func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
t.encodeHashableFields(bw)
@ -202,12 +174,10 @@ func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
// encodeHashableFields encodes the fields that are not used for
// signing the transaction, which are all fields except the scripts.
func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
noData := t.Type == ContractType
if t.Data == nil && !noData {
bw.Err = errors.New("transaction has no data")
if len(t.Script) == 0 {
bw.Err = errors.New("transaction has no script")
return
}
bw.WriteB(byte(t.Type))
bw.WriteB(byte(t.Version))
bw.WriteU32LE(t.Nonce)
t.Sender.EncodeBinary(bw)
@ -215,22 +185,13 @@ func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
t.NetworkFee.EncodeBinary(bw)
bw.WriteU32LE(t.ValidUntilBlock)
// Underlying TXer.
if !noData {
t.Data.EncodeBinary(bw)
}
// Attributes
bw.WriteArray(t.Attributes)
// Cosigners
bw.WriteArray(t.Cosigners)
// Inputs
bw.WriteArray(t.Inputs)
// Outputs
bw.WriteArray(t.Outputs)
bw.WriteVarBytes(t.Script)
}
// createHash creates the hash of the transaction.
@ -248,16 +209,6 @@ func (t *Transaction) createHash() error {
return nil
}
// GroupOutputByAssetID groups all TX outputs by their assetID.
func (t Transaction) GroupOutputByAssetID() map[util.Uint256][]*Output {
m := make(map[util.Uint256][]*Output)
for i := range t.Outputs {
hash := t.Outputs[i].AssetID
m[hash] = append(m[hash], &t.Outputs[i])
}
return m
}
// GetSignedPart returns a part of the transaction which must be signed.
func (t *Transaction) GetSignedPart() []byte {
buf := io.NewBufBinWriter()
@ -300,7 +251,6 @@ func (t *Transaction) FeePerByte() util.Fixed8 {
type transactionJSON struct {
TxID util.Uint256 `json:"txid"`
Size int `json:"size"`
Type TXType `json:"type"`
Version uint8 `json:"version"`
Nonce uint32 `json:"nonce"`
Sender string `json:"sender"`
@ -309,14 +259,8 @@ type transactionJSON struct {
ValidUntilBlock uint32 `json:"valid_until_block"`
Attributes []Attribute `json:"attributes"`
Cosigners []Cosigner `json:"cosigners"`
Inputs []Input `json:"vin"`
Outputs []Output `json:"vout"`
Script string `json:"script"`
Scripts []Witness `json:"scripts"`
Claims []Input `json:"claims,omitempty"`
Script string `json:"script,omitempty"`
Gas util.Fixed8 `json:"gas,omitempty"`
Asset *registeredAsset `json:"asset,omitempty"`
}
// MarshalJSON implements json.Marshaler interface.
@ -324,36 +268,17 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
tx := transactionJSON{
TxID: t.Hash(),
Size: io.GetVarSize(t),
Type: t.Type,
Version: t.Version,
Nonce: t.Nonce,
Sender: address.Uint160ToString(t.Sender),
ValidUntilBlock: t.ValidUntilBlock,
Attributes: t.Attributes,
Cosigners: t.Cosigners,
Inputs: t.Inputs,
Outputs: t.Outputs,
Script: hex.EncodeToString(t.Script),
Scripts: t.Scripts,
SystemFee: t.SystemFee,
NetworkFee: t.NetworkFee,
}
switch t.Type {
case ClaimType:
tx.Claims = t.Data.(*ClaimTX).Claims
case InvocationType:
tx.Script = hex.EncodeToString(t.Data.(*InvocationTX).Script)
tx.Gas = t.Data.(*InvocationTX).Gas
case RegisterType:
transaction := *t.Data.(*RegisterTX)
tx.Asset = &registeredAsset{
AssetType: transaction.AssetType,
Name: json.RawMessage(transaction.Name),
Amount: transaction.Amount,
Precision: transaction.Precision,
Owner: transaction.Owner,
Admin: address.Uint160ToString(transaction.Admin),
}
}
return json.Marshal(tx)
}
@ -363,14 +288,11 @@ func (t *Transaction) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, tx); err != nil {
return err
}
t.Type = tx.Type
t.Version = tx.Version
t.Nonce = tx.Nonce
t.ValidUntilBlock = tx.ValidUntilBlock
t.Attributes = tx.Attributes
t.Cosigners = tx.Cosigners
t.Inputs = tx.Inputs
t.Outputs = tx.Outputs
t.Scripts = tx.Scripts
t.SystemFee = tx.SystemFee
t.NetworkFee = tx.NetworkFee
@ -379,38 +301,9 @@ func (t *Transaction) UnmarshalJSON(data []byte) error {
return errors.New("cannot unmarshal tx: bad sender")
}
t.Sender = sender
switch tx.Type {
case ClaimType:
t.Data = &ClaimTX{
Claims: tx.Claims,
}
case InvocationType:
bytes, err := hex.DecodeString(tx.Script)
if err != nil {
return err
}
t.Data = &InvocationTX{
Script: bytes,
Gas: tx.Gas,
Version: tx.Version,
}
case RegisterType:
admin, err := address.StringToUint160(tx.Asset.Admin)
if err != nil {
return err
}
t.Data = &RegisterTX{
AssetType: tx.Asset.AssetType,
Name: string(tx.Asset.Name),
Amount: tx.Asset.Amount,
Precision: tx.Asset.Precision,
Owner: tx.Asset.Owner,
Admin: admin,
}
case ContractType:
t.Data = &ContractTX{}
case IssueType:
t.Data = &IssueTX{}
t.Script, err = hex.DecodeString(tx.Script)
if err != nil {
return err
}
if t.Hash() != tx.TxID {
return errors.New("txid doesn't match transaction hash")

View file

@ -4,7 +4,6 @@ import (
"encoding/hex"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert"
@ -36,31 +35,6 @@ func TestWitnessEncodeDecode(t *testing.T) {
// TODO NEO3.0: update binary
/*
func TestDecodeEncodeClaimTX(t *testing.T) {
tx := decodeTransaction(rawClaimTX, t)
assert.Equal(t, tx.Type, ClaimType)
assert.IsType(t, tx.Data, &ClaimTX{})
claimTX := tx.Data.(*ClaimTX)
assert.Equal(t, 4, len(claimTX.Claims))
assert.Equal(t, 0, len(tx.Attributes))
assert.Equal(t, 0, len(tx.Inputs))
assert.Equal(t, 1, len(tx.Outputs))
assert.Equal(t, "AQJseD8iBmCD4sgfHRhMahmoi9zvopG6yz", address.Uint160ToString(tx.Outputs[0].ScriptHash))
assert.Equal(t, "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7", tx.Outputs[0].AssetID.StringLE())
assert.Equal(t, tx.Outputs[0].Amount.String(), "0.06247739")
invoc := "40456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb"
verif := "2103945fae1ed3c31d778f149192b76734fcc951b400ba3598faa81ff92ebe477eacac"
assert.Equal(t, 1, len(tx.Scripts))
assert.Equal(t, invoc, hex.EncodeToString(tx.Scripts[0].InvocationScript))
assert.Equal(t, verif, hex.EncodeToString(tx.Scripts[0].VerificationScript))
data, err := testserdes.EncodeBinary(tx)
assert.NoError(t, err)
assert.Equal(t, rawClaimTX, hex.EncodeToString(data))
hash := "2c6a45547b3898318e400e541628990a07acb00f3b9a15a8e966ae49525304da"
assert.Equal(t, hash, tx.hash.StringLE())
}
func TestDecodeEncodeInvocationTX(t *testing.T) {
tx := decodeTransaction(rawInvocationTX, t)
@ -87,119 +61,35 @@ func TestDecodeEncodeInvocationTX(t *testing.T) {
}
*/
func TestNewInvocationTX(t *testing.T) {
func TestNew(t *testing.T) {
script := []byte{0x51}
tx := NewInvocationTX(script, 1)
txData := tx.Data.(*InvocationTX)
assert.Equal(t, InvocationType, tx.Type)
assert.Equal(t, tx.Version, txData.Version)
assert.Equal(t, script, txData.Script)
tx := New(script, 1)
assert.Equal(t, util.Fixed8(1), tx.SystemFee)
assert.Equal(t, script, tx.Script)
// Update hash fields to match tx2 that is gonna autoupdate them on decode.
_ = tx.Hash()
testserdes.EncodeDecodeBinary(t, tx, new(Transaction))
}
func TestEncodingTXWithNoData(t *testing.T) {
func TestEncodingTXWithNoScript(t *testing.T) {
_, err := testserdes.EncodeBinary(new(Transaction))
require.Error(t, err)
}
func TestMarshalUnmarshalJSONContractTX(t *testing.T) {
tx := NewContractTX()
tx.Outputs = []Output{{
AssetID: util.Uint256{1, 2, 3, 4},
Amount: 567,
ScriptHash: util.Uint160{7, 8, 9, 10},
Position: 13,
}}
tx.Scripts = []Witness{{
InvocationScript: []byte{5, 3, 1},
VerificationScript: []byte{2, 4, 6},
}}
tx.Data = &ContractTX{}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONClaimTX(t *testing.T) {
tx := &Transaction{
Type: ClaimType,
Version: 0,
Data: &ClaimTX{Claims: []Input{
{
PrevHash: util.Uint256{1, 2, 3, 4},
PrevIndex: uint16(56),
},
}},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
func TestDecodingTXWithNoScript(t *testing.T) {
txBin, err := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
require.NoError(t, err)
err = testserdes.DecodeBinary(txBin, new(Transaction))
require.Error(t, err)
}
func TestMarshalUnmarshalJSONInvocationTX(t *testing.T) {
tx := &Transaction{
Type: InvocationType,
Version: 3,
Data: &InvocationTX{
Script: []byte{1, 2, 3, 4},
Gas: util.Fixed8FromFloat(100),
Version: 3,
},
Version: 0,
Script: []byte{1, 2, 3, 4},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))
}
func TestMarshalUnmarshalJSONRegisterTX(t *testing.T) {
tx := &Transaction{
Type: RegisterType,
Version: 5,
Data: &RegisterTX{
AssetType: 0,
Name: `[{"lang":"zh-CN","name":"小蚁股"},{"lang":"en","name":"AntShare"}]`,
Amount: 1000000,
Precision: 0,
Owner: keys.PublicKey{},
Admin: util.Uint160{},
},
Attributes: []Attribute{},
Inputs: []Input{{
PrevHash: util.Uint256{5, 6, 7, 8},
PrevIndex: uint16(12),
}},
Outputs: []Output{{
AssetID: util.Uint256{1, 2, 3},
Amount: util.Fixed8FromInt64(1),
ScriptHash: util.Uint160{1, 2, 3},
Position: 0,
}},
Scripts: []Witness{},
Trimmed: false,
Scripts: []Witness{},
Trimmed: false,
}
testserdes.MarshalUnmarshalJSON(t, tx, new(Transaction))

View file

@ -1,9 +0,0 @@
package transaction
import "github.com/nspcc-dev/neo-go/pkg/io"
// TXer is interface that can act as the underlying data of
// a transaction.
type TXer interface {
io.Serializable
}

View file

@ -1,71 +0,0 @@
package transaction
import (
"strings"
"github.com/pkg/errors"
)
// TXType is the type of a transaction.
type TXType uint8
// Constants for all valid transaction types.
const (
IssueType TXType = 0x01
ClaimType TXType = 0x02
RegisterType TXType = 0x40
ContractType TXType = 0x80
InvocationType TXType = 0xd1
)
// String implements the stringer interface.
func (t TXType) String() string {
switch t {
case IssueType:
return "IssueTransaction"
case ClaimType:
return "ClaimTransaction"
case RegisterType:
return "RegisterTransaction"
case ContractType:
return "ContractTransaction"
case InvocationType:
return "InvocationTransaction"
default:
return "UnknownTransaction"
}
}
// MarshalJSON implements the json marshaller interface.
func (t TXType) MarshalJSON() ([]byte, error) {
return []byte(`"` + t.String() + `"`), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (t *TXType) UnmarshalJSON(data []byte) error {
l := len(data)
if l < 2 || data[0] != '"' || data[l-1] != '"' {
return errors.New("wrong format")
}
var err error
*t, err = TXTypeFromString(string(data[1 : l-1]))
return err
}
// TXTypeFromString searches for TXType by string name.
func TXTypeFromString(jsonString string) (TXType, error) {
switch jsonString = strings.TrimSpace(jsonString); jsonString {
case "IssueTransaction":
return IssueType, nil
case "ClaimTransaction":
return ClaimType, nil
case "RegisterTransaction":
return RegisterType, nil
case "ContractTransaction":
return ContractType, nil
case "InvocationTransaction":
return InvocationType, nil
default:
return 0, errors.New("unknown state")
}
}

View file

@ -46,48 +46,18 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error)
base := block.Base{
Version: 0,
PrevHash: util.Uint256{},
Timestamp: uint64(time.Date(2016, 7, 15, 15, 8, 21, 0, time.UTC).Unix()),
Timestamp: uint64(time.Date(2016, 7, 15, 15, 8, 21, 0, time.UTC).Unix()) * 1000, // Milliseconds.
Index: 0,
NextConsensus: nextConsensus,
Script: transaction.Witness{
InvocationScript: []byte{},
VerificationScript: []byte{byte(opcode.OLDPUSH1)},
},
}
rawScript, err := smartcontract.CreateMultiSigRedeemScript(
len(cfg.StandbyValidators)/2+1,
validators,
)
if err != nil {
return nil, err
}
scriptOut := hash.Hash160(rawScript)
issueTx := transaction.NewIssueTX()
// TODO NEO3.0: nonce should be constant to avoid variability of genesis block
issueTx.Nonce = 0
issueTx.Sender = hash.Hash160([]byte{byte(opcode.OLDPUSH1)})
issueTx.Outputs = []transaction.Output{
{
AssetID: governingTokenTX.Hash(),
Amount: governingTokenTX.Data.(*transaction.RegisterTX).Amount,
ScriptHash: scriptOut,
},
}
issueTx.Scripts = []transaction.Witness{
{
InvocationScript: []byte{},
VerificationScript: []byte{byte(opcode.OLDPUSH1)},
VerificationScript: []byte{byte(opcode.PUSH1)},
},
}
b := &block.Block{
Base: base,
Transactions: []*transaction.Transaction{
&governingTokenTX,
&utilityTokenTX,
issueTx,
deployNativeContracts(),
},
ConsensusData: block.ConsensusData{
@ -107,7 +77,7 @@ func deployNativeContracts() *transaction.Transaction {
buf := io.NewBufBinWriter()
emit.Syscall(buf.BinWriter, "Neo.Native.Deploy")
script := buf.Bytes()
tx := transaction.NewInvocationTX(script, 0)
tx := transaction.New(script, 0)
tx.Nonce = 0
tx.Sender = hash.Hash160([]byte{byte(opcode.PUSH1)})
tx.Scripts = []transaction.Witness{
@ -119,45 +89,6 @@ func deployNativeContracts() *transaction.Transaction {
return tx
}
func init() {
admin := hash.Hash160([]byte{byte(opcode.OLDPUSH1)})
registerTX := &transaction.RegisterTX{
AssetType: transaction.GoverningToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]",
Amount: util.Fixed8FromInt64(100000000),
Precision: 0,
Admin: admin,
}
governingTokenTX = *transaction.NewRegisterTX(registerTX)
// TODO NEO3.0: nonce should be constant to avoid variability of token hash
governingTokenTX.Nonce = 0
governingTokenTX.Sender = hash.Hash160([]byte{byte(opcode.OLDPUSH1)})
admin = hash.Hash160([]byte{0x00})
registerTX = &transaction.RegisterTX{
AssetType: transaction.UtilityToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]",
Amount: calculateUtilityAmount(),
Precision: 8,
Admin: admin,
}
utilityTokenTX = *transaction.NewRegisterTX(registerTX)
// TODO NEO3.0: nonce should be constant to avoid variability of token hash
utilityTokenTX.Nonce = 0
utilityTokenTX.Sender = hash.Hash160([]byte{byte(opcode.OLDPUSH1)})
}
// GoverningTokenID returns the governing token (NEO) hash.
func GoverningTokenID() util.Uint256 {
return governingTokenTX.Hash()
}
// UtilityTokenID returns the utility token (GAS) hash.
func UtilityTokenID() util.Uint256 {
return utilityTokenTX.Hash()
}
func getValidators(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) {
validators := make([]*keys.PublicKey, len(cfg.StandbyValidators))
for i, pubKeyStr := range cfg.StandbyValidators {

View file

@ -20,7 +20,7 @@ func TestGenesisBlockMainNet(t *testing.T) {
// have been changed. Consequently, hash of the genesis block has been changed.
// Update expected genesis block hash for better times.
// Old hash is "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"
expect := "1d4156d233220b893797a684fbb827bb2163b5042edd10653bbc1b2769adbb8d"
expect := "e4cfc549c87d4ab7b570c368d05853ffb70eb9ef0f7d9c7a2e6e9e5d713ebbf4"
assert.Equal(t, expect, block.Hash().StringLE())
}
@ -42,19 +42,3 @@ func TestGetConsensusAddressMainNet(t *testing.T) {
assert.Equal(t, consensusScript, script.String())
assert.Equal(t, consensusAddr, address.Uint160ToString(script))
}
func TestUtilityTokenTX(t *testing.T) {
//TODO: After we added Nonce field to transaction.Transaction, UtilityTockenTx hash
// has been changed. Update it for better times.
// Old hash is "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
expect := "f882fb865bab84b99623f21eedd902286af7da8d8a4609d7acefce04c851dc1c"
assert.Equal(t, expect, UtilityTokenID().StringLE())
}
func TestGoverningTokenTX(t *testing.T) {
//TODO: After we added Nonce field to transaction.Transaction, GoveringTockenTx hash
// has been changed. Update it for better times.
// Old hash is "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"
expect := "1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78"
assert.Equal(t, expect, GoverningTokenID().StringLE())
}

View file

@ -1,97 +0,0 @@
/*
Package asset provides functions to work with regular UTXO assets (like NEO or GAS).
Mostly these are getters for Asset structure, but you can also create new assets
and renew them (although it's recommended to use NEP-5 standard for new tokens).
*/
package asset
// Asset represents NEO asset type that is used in interop functions, it's
// an opaque data structure that you can get data from only using functions from
// this package. It's similar in function to the Asset class in the Neo .net
// framework. To be able to use it you either need to get an existing Asset via
// blockchain.GetAsset function or create a new one via Create.
type Asset struct{}
// GetAssetID returns ID (256-bit ID of Register transaction for this asset in BE
// representation) of the given asset. It uses `Neo.Asset.GetAssetId` syscall
// internally.
func GetAssetID(a Asset) []byte {
return nil
}
// GetAssetType returns type of the given asset as a byte value. The value
// returned can be interpreted as a bit field with the following meaning:
// CreditFlag = 0x40
// DutyFlag = 0x80
// SystemShare = 0x00
// SystemCoin = 0x01
// Currency = 0x08
// Share = DutyFlag | 0x10
// Invoice = DutyFlag | 0x18
// Token = CreditFlag | 0x20
// It uses `Neo.Asset.GetAssetType` syscall internally.
func GetAssetType(a Asset) byte {
return 0x00
}
// GetAmount returns the total amount of the given asset as an integer
// multiplied by 10⁸. This value is the maximum possible circulating quantity of
// Asset. The function uses `Neo.Asset.GetAmount` syscall internally.
func GetAmount(a Asset) int {
return 0
}
// GetAvailable returns the amount of Asset currently available on the
// blockchain. It uses the same encoding as the result of GetAmount and its
// value can never exceed the value returned by GetAmount. This function uses
// `Neo.Asset.GetAvailable` syscall internally.
func GetAvailable(a Asset) int {
return 0
}
// GetPrecision returns precision of the given Asset. It uses
// `Neo.Asset.GetPrecision` syscall internally.
func GetPrecision(a Asset) byte {
return 0x00
}
// GetOwner returns the owner of the given Asset. It's represented as a
// serialized (in compressed form) public key (33 bytes long). This function
// uses `Neo.Asset.GetOwner` syscall internally.
func GetOwner(a Asset) []byte {
return nil
}
// GetAdmin returns the admin of the given Asset represented as a 160 bit hash
// in BE form (contract script hash). Admin can modify attributes of this Asset.
// This function uses `Neo.Asset.GetAdmin` syscall internally.
func GetAdmin(a Asset) []byte {
return nil
}
// GetIssuer returns the issuer of the given Asset represented as a 160 bit hash
// in BE form (contract script hash). Issuer can issue new tokens for this Asset.
// This function uses `Neo.Asset.GetIssuer` syscall internally.
func GetIssuer(a Asset) []byte {
return nil
}
// Create registers a new asset on the blockchain (similar to old Register
// transaction). `assetType` parameter has the same set of possible values as
// GetAssetType result, `amount` must be multiplied by 10⁸, `precision` limits
// the smallest possible amount of new Asset to 10⁻ⁿ (where n is precision which
// can't exceed 8), `owner` is a public key of the owner in compressed serialized
// form (33 bytes), `admin` and `issuer` should be represented as 20-byte slices
// storing 160-bit hash in BE form. Created Asset is set to expire in one year,
// so you need to renew it in time. If successful, this function returns a new
// Asset. It uses `Neo.Asset.Create` syscall internally.
func Create(assetType byte, name string, amount int, precision byte, owner, admin, issuer []byte) Asset {
return Asset{}
}
// Renew renews (make available for use) existing asset by the specified number
// of years. It returns the last block number when this asset will be active.
// It uses `Neo.Asset.Renew` syscall internally.
func Renew(asset Asset, years int) int {
return 0
}

View file

@ -5,7 +5,6 @@ package blockchain
import (
"github.com/nspcc-dev/neo-go/pkg/interop/account"
"github.com/nspcc-dev/neo-go/pkg/interop/asset"
"github.com/nspcc-dev/neo-go/pkg/interop/block"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/header"
@ -73,11 +72,3 @@ func GetAccount(scriptHash []byte) account.Account {
func GetValidators() [][]byte {
return nil
}
// GetAsset returns asset found by the given asset ID (256 bit in BE format
// represented as a slice of 32 bytes). Refer to the `asset` package for
// possible uses of returned structure. This function uses
// `Neo.Blockchain.GetAsset` syscall.
func GetAsset(assetID []byte) asset.Asset {
return asset.Asset{}
}

View file

@ -1,22 +0,0 @@
/*
Package input provides functions dealing with transaction inputs.
*/
package input
// Input is an opaque data structure that can only be created by
// transaction.GetInputs and it represents transaction's input. It's similar
// to Neo .net framework's TransactionInput.
type Input struct{}
// GetHash returns the hash stored in the given input (which also is a
// transaction ID represented as 32 byte slice containing 256 bit BE value).
// It uses `Neo.Input.GetHash` syscall.
func GetHash(in Input) []byte {
return nil
}
// GetIndex returns the index stored in the given input (which is a
// transaction's output number). It uses `Neo.Input.GetIndex` syscall.
func GetIndex(in Input) int {
return 0
}

View file

@ -1,28 +0,0 @@
/*
Package output provides functions dealing with transaction outputs.
*/
package output
// Output is an opaque data structure that can only be created by
// transaction.GetOutputs and it represents transaction's output. It's similar
// to Neo .net framework's TransactionOutput.
type Output struct{}
// GetAssetID returns the asset ID (256 bit BE value in a 32 byte slice) of the
// given output. It uses `Neo.Output.GetAssetId` syscall.
func GetAssetID(out Output) []byte {
return nil
}
// GetValue returns the value (asset quantity) of the given output. It uses
// `Neo.Output.GetValue` syscall.
func GetValue(out Output) int {
return 0
}
// GetScriptHash returns the script hash (receiver's address represented as
// 20 byte slice containing 160 bit BE value) of the given output. It uses
// `Neo.Output.GetScriptHash` syscall.
func GetScriptHash(out Output) []byte {
return nil
}

View file

@ -5,8 +5,6 @@ package transaction
import (
"github.com/nspcc-dev/neo-go/pkg/interop/attribute"
"github.com/nspcc-dev/neo-go/pkg/interop/input"
"github.com/nspcc-dev/neo-go/pkg/interop/output"
"github.com/nspcc-dev/neo-go/pkg/interop/witness"
)
@ -21,22 +19,6 @@ func GetHash(t Transaction) []byte {
return nil
}
// GetType returns the type of the given transaction. Possible values:
// MinerTransaction = 0x00
// IssueTransaction = 0x01
// ClaimTransaction = 0x02
// EnrollmentTransaction = 0x20
// RegisterTransaction = 0x40
// ContractTransaction = 0x80
// StateType = 0x90
// AgencyTransaction = 0xb0
// PublishTransaction = 0xd0
// InvocationTransaction = 0xd1
// It uses `Neo.Transaction.GetType` syscall.
func GetType(t Transaction) byte {
return 0x00
}
// GetAttributes returns a slice of attributes for agiven transaction. Refer to
// attribute package on how to use them. This function uses
// `Neo.Transaction.GetAttributes` syscall.
@ -44,42 +26,6 @@ func GetAttributes(t Transaction) []attribute.Attribute {
return []attribute.Attribute{}
}
// GetReferences returns a slice of references for a given Transaction. Elements
// of this slice can be casted to any of input.Input or output.Output, depending
// on which information you're interested in (as reference technically contains
// both input and corresponding output), refer to input and output package on
// how to use them. This function uses `Neo.Transaction.GetReferences` syscall.
func GetReferences(t Transaction) []interface{} {
return []interface{}{}
}
// GetUnspentCoins returns a slice of not yet spent ouputs of a given transaction.
// This function uses `Neo.Transaction.GetUnspentCoint` syscall.
func GetUnspentCoins(t Transaction) []output.Output {
return []output.Output{}
}
// GetInputs returns a slice of inputs of a given Transaction. Refer to input
// package on how to use them. This function uses `Neo.Transaction.GetInputs`
// syscall.
func GetInputs(t Transaction) []input.Input {
return []input.Input{}
}
// GetOutputs returns a slice of outputs of a given Transaction. Refer to output
// package on how to use them. This function uses `Neo.Transaction.GetOutputs`
// syscall.
func GetOutputs(t Transaction) []output.Output {
return []output.Output{}
}
// GetScript returns the script stored in a given Invocation transaction.
// Calling it for any other Transaction type would lead to failure. It uses
// `Neo.InvocationTransaction.GetScript` syscall.
func GetScript(t Transaction) []byte {
return nil
}
// GetWitnesses returns a slice of witnesses of a given Transaction. Refer to
// witness package on how to use them. This function uses
// `Neo.Transaction.GetWitnesses` syscall.

View file

@ -25,17 +25,13 @@ type testChain struct {
blockheight uint32
}
func (chain testChain) ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithFee {
func (chain testChain) ApplyPolicyToTxSet([]*transaction.Transaction) []*transaction.Transaction {
panic("TODO")
}
func (chain testChain) GetConfig() config.ProtocolConfiguration {
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) CalculateClaimable(int64, uint32, uint32) util.Fixed8 {
panic("TODO")
}
@ -77,9 +73,6 @@ func (chain testChain) GetHeader(hash util.Uint256) (*block.Header, error) {
panic("TODO")
}
func (chain testChain) GetAssetState(util.Uint256) *state.Asset {
panic("TODO")
}
func (chain testChain) GetAccountState(util.Uint160) *state.Account {
panic("TODO")
}
@ -126,10 +119,6 @@ func (chain testChain) GetTransaction(util.Uint256) (*transaction.Transaction, u
panic("TODO")
}
func (chain testChain) GetUnspentCoinState(util.Uint256) *state.UnspentCoin {
panic("TODO")
}
func (chain testChain) GetMemPool() *mempool.Pool {
panic("TODO")
}
@ -138,6 +127,10 @@ func (chain testChain) IsLowPriority(util.Fixed8) bool {
panic("TODO")
}
func (chain testChain) GetGoverningTokenBalance(acc util.Uint160) (util.Fixed8, uint32) {
panic("TODO")
}
func (chain testChain) GetUtilityTokenBalance(uint160 util.Uint160) util.Fixed8 {
panic("TODO")
}

View file

@ -11,12 +11,9 @@ import (
"sync"
"time"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/pkg/errors"
)
@ -45,13 +42,6 @@ type Client struct {
// All values are optional. If any duration is not specified
// a default of 4 seconds will be used.
type Options struct {
// Balancer is an implementation of request.BalanceGetter interface,
// if not set then the default Client's implementation will be used, but
// it relies on server support for `getunspents` RPC call which is
// standard for neo-go, but only implemented as a plugin for C# node. So
// you can override it here to use NeoScanServer for example.
Balancer request.BalanceGetter
// Cert is a client-side certificate, it doesn't work at the moment along
// with the other two options below.
Cert string
@ -107,9 +97,6 @@ func New(ctx context.Context, endpoint string, opts Options) (*Client, error) {
wifMu: new(sync.Mutex),
endpoint: url,
}
if opts.Balancer == nil {
opts.Balancer = cl
}
cl.opts = opts
cl.requestF = cl.makeHTTPRequest
return cl, nil
@ -140,27 +127,6 @@ func (c *Client) SetWIF(wif string) error {
return nil
}
// CalculateInputs implements request.BalanceGetter interface and returns inputs
// array for the specified amount of given asset belonging to specified address.
// This implementation uses GetUnspents JSON-RPC call internally, so make sure
// your RPC server supports that.
func (c *Client) CalculateInputs(address string, asset util.Uint256, cost util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
var utxos state.UnspentBalances
resp, err := c.GetUnspents(address)
if err != nil {
return nil, util.Fixed8(0), errors.Wrapf(err, "cannot get balance for address %v", address)
}
for _, ubi := range resp.Balance {
if asset.Equals(ubi.AssetHash) {
utxos = ubi.Unspents
break
}
}
return unspentsToInputs(utxos, cost)
}
func (c *Client) performRequest(method string, p request.RawParams, v interface{}) error {
var r = request.Raw{
JSONRPC: request.JSONRPCVersion,

View file

@ -18,16 +18,13 @@ TODO:
Supported methods
getaccountstate
getapplicationlog
getassetstate
getbestblockhash
getblock
getblockcount
getblockhash
getblockheader
getblocksysfee
getclaimable
getconnectioncount
getcontractstate
getnep5balances
@ -37,9 +34,7 @@ Supported methods
getrawtransaction
getstorage
gettransactionheight
gettxout
getunclaimed
getunspents
getunclaimedgas
getvalidators
getversion
invoke
@ -56,7 +51,6 @@ Unsupported methods
getbalance
getmetricblocktimestamp
getnewaddress
getunclaimedgas
getwalletheight
importprivkey
listaddress

View file

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
)
@ -23,11 +24,16 @@ func Example() {
os.Exit(1)
}
resp, err := c.GetAccountState("ATySFJAbLW7QHsZGHScLhxq6EyNBxx3eFP")
addr, err := address.StringToUint160("ATySFJAbLW7QHsZGHScLhxq6EyNBxx3eFP")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(resp.ScriptHash)
resp, err := c.GetNEP5Balances(addr)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(resp.Address)
fmt.Println(resp.Balances)
}

View file

@ -1,125 +0,0 @@
package client
import (
"encoding/json"
"errors"
"net/http"
"sort"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/util"
errs "github.com/pkg/errors"
)
/*
Definition of types, helper functions and variables
required for calculation of transaction inputs using
NeoScan API.
*/
type (
// NeoScanServer stores NEOSCAN URL and API path.
NeoScanServer struct {
URL string // "protocol://host:port/"
Path string // path to API endpoint without wallet address
}
// Unspent stores Unspents per asset
Unspent struct {
Unspent state.UnspentBalances
Asset string // "NEO" / "GAS"
Amount util.Fixed8 // total unspent of this asset
}
// NeoScanBalance is a struct of NeoScan response to 'get_balance' request
NeoScanBalance struct {
Balance []*Unspent
Address string
}
)
// GetBalance performs a request to get balance for the address specified.
func (s NeoScanServer) GetBalance(address string) ([]*Unspent, error) {
var (
err error
req *http.Request
res *http.Response
balance NeoScanBalance
client = http.Client{}
balanceURL = s.URL + s.Path
)
if req, err = http.NewRequest(http.MethodGet, balanceURL+address, nil); err != nil {
return nil, errs.Wrap(err, "Failed to compose HTTP request")
}
if res, err = client.Do(req); err != nil {
return nil, errs.Wrap(err, "Failed to perform HTTP request")
}
defer res.Body.Close()
if err = json.NewDecoder(res.Body).Decode(&balance); err != nil {
return nil, errs.Wrap(err, "Failed to decode HTTP response")
}
return balance.Balance, nil
}
func filterSpecificAsset(asset string, balance []*Unspent, assetBalance *Unspent) {
for _, us := range balance {
if us.Asset == asset {
assetBalance.Unspent = us.Unspent
assetBalance.Asset = us.Asset
assetBalance.Amount = us.Amount
return
}
}
}
// CalculateInputs creates input transactions for the specified amount of given asset belonging to specified address.
func (s NeoScanServer) CalculateInputs(address string, assetIDUint util.Uint256, cost util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
var (
err error
us []*Unspent
assetUnspent Unspent
assetID = result.GlobalAssets[assetIDUint.StringLE()]
)
if us, err = s.GetBalance(address); err != nil {
return nil, util.Fixed8(0), errs.Wrapf(err, "Cannot get balance for address %v", address)
}
filterSpecificAsset(assetID, us, &assetUnspent)
return unspentsToInputs(assetUnspent.Unspent, cost)
}
// unspentsToInputs uses UnspentBalances to create a slice of inputs for a new
// transcation containing the required amount of asset.
func unspentsToInputs(utxos state.UnspentBalances, required util.Fixed8) ([]transaction.Input, util.Fixed8, error) {
var (
num, i uint16
selected = util.Fixed8(0)
)
sort.Sort(utxos)
for _, us := range utxos {
if selected >= required {
break
}
selected += us.Value
num++
}
if selected < required {
return nil, util.Fixed8(0), errors.New("cannot compose inputs for transaction; check sender balance")
}
inputs := make([]transaction.Input, 0, num)
for i = 0; i < num; i++ {
inputs = append(inputs, transaction.Input{
PrevHash: utxos[i].Tx,
PrevIndex: utxos[i].Index,
})
}
return inputs, selected, nil
}

View file

@ -5,11 +5,9 @@ import (
"errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
@ -94,22 +92,23 @@ func (c *Client) NEP5TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
return wallet.NewToken(tokenHash, name, symbol, decimals), nil
}
// TransferNEP5 creates an invocation transaction that invokes 'transfer' method
// on a given token to move specified amount of NEP5 assets (in FixedN format
// using contract's number of decimals) to given account.
func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *wallet.Token, amount int64, gas util.Fixed8) (util.Uint256, error) {
// CreateNEP5TransferTx creates an invocation transaction for the 'transfer'
// method of a given contract (token) to move specified amount of NEP5 assets
// (in FixedN format using contract's number of decimals) to given account and
// returns it. The returned transaction is not signed.
func (c *Client) CreateNEP5TransferTx(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas util.Fixed8) (*transaction.Transaction, error) {
from, err := address.StringToUint160(acc.Address)
if err != nil {
return util.Uint256{}, fmt.Errorf("bad account address: %v", err)
return nil, fmt.Errorf("bad account address: %v", err)
}
// Note: we don't use invoke function here because it requires
// 2 round trips instead of one.
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, token.Hash, "transfer", from, to, amount)
emit.AppCallWithOperationAndArgs(w.BinWriter, token, "transfer", from, to, amount)
emit.Opcode(w.BinWriter, opcode.ASSERT)
script := w.Bytes()
tx := transaction.NewInvocationTX(script, gas)
tx := transaction.New(script, gas)
tx.Sender = from
tx.Cosigners = []transaction.Cosigner{
{
@ -122,11 +121,11 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *walle
result, err := c.InvokeScript(hex.EncodeToString(script))
if err != nil {
return util.Uint256{}, fmt.Errorf("can't add system fee to transaction: %v", err)
return nil, fmt.Errorf("can't add system fee to transaction: %v", err)
}
gasConsumed, err := util.Fixed8FromString(result.GasConsumed)
if err != nil {
return util.Uint256{}, fmt.Errorf("can't add system fee to transaction: %v", err)
return nil, fmt.Errorf("can't add system fee to transaction: %v", err)
}
if gasConsumed > 0 {
tx.SystemFee = gasConsumed
@ -134,16 +133,25 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *walle
tx.ValidUntilBlock, err = c.CalculateValidUntilBlock()
if err != nil {
return util.Uint256{}, fmt.Errorf("can't calculate validUntilBlock: %v", err)
}
if err := request.AddInputsAndUnspentsToTx(tx, acc.Address, core.UtilityTokenID(), gas, c); err != nil {
return util.Uint256{}, fmt.Errorf("can't add GAS to transaction: %v", err)
return nil, fmt.Errorf("can't calculate validUntilBlock: %v", err)
}
err = c.AddNetworkFee(tx, acc)
if err != nil {
return util.Uint256{}, fmt.Errorf("can't add network fee to transaction: %v", err)
return nil, fmt.Errorf("can't add network fee to transaction: %v", err)
}
return tx, nil
}
// TransferNEP5 creates an invocation transaction that invokes 'transfer' method
// on a given token to move specified amount of NEP5 assets (in FixedN format
// using contract's number of decimals) to given account and sends it to the
// network returning just a hash of it.
func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas util.Fixed8) (util.Uint256, error) {
tx, err := c.CreateNEP5TransferTx(acc, to, token, amount, gas)
if err != nil {
return util.Uint256{}, err
}
if err := acc.SignTx(tx); err != nil {

View file

@ -2,6 +2,7 @@ package client
import (
"encoding/hex"
"strconv"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
@ -16,18 +17,6 @@ import (
"github.com/pkg/errors"
)
// GetAccountState returns detailed information about a NEO account.
func (c *Client) GetAccountState(address string) (*result.AccountState, error) {
var (
params = request.NewRawParams(address)
resp = &result.AccountState{}
)
if err := c.performRequest("getaccountstate", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetApplicationLog returns the contract log based on the specified txid.
func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, error) {
var (
@ -40,18 +29,6 @@ func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, e
return resp, nil
}
// GetAssetState queries the asset information, based on the specified asset number.
func (c *Client) GetAssetState(hash util.Uint256) (*result.AssetState, error) {
var (
params = request.NewRawParams(hash.StringLE())
resp = &result.AssetState{}
)
if err := c.performRequest("getassetstate", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetBestBlockHash returns the hash of the tallest block in the main chain.
func (c *Client) GetBestBlockHash() (util.Uint256, error) {
var resp = util.Uint256{}
@ -187,16 +164,6 @@ func (c *Client) GetBlockSysFee(index uint32) (util.Fixed8, error) {
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
}
// GetConnectionCount returns the current number of connections for the node.
func (c *Client) GetConnectionCount() (int, error) {
var (
@ -329,41 +296,20 @@ func (c *Client) GetTransactionHeight(hash util.Uint256) (uint32, error) {
return resp, nil
}
// GetTxOut returns the corresponding unspent transaction output information (returned change),
// based on the specified hash and index.
func (c *Client) GetTxOut(hash util.Uint256, num int) (*result.TransactionOutput, error) {
var (
params = request.NewRawParams(hash.StringLE(), num)
resp = &result.TransactionOutput{}
)
if err := c.performRequest("gettxout", params, resp); err != nil {
return resp, err
}
return resp, nil
}
// GetUnclaimed returns unclaimed GAS amount of the specified address.
func (c *Client) GetUnclaimed(address string) (*result.Unclaimed, error) {
// GetUnclaimedGas returns unclaimed GAS amount for the specified address.
func (c *Client) GetUnclaimedGas(address string) (util.Fixed8, error) {
var (
params = request.NewRawParams(address)
resp = &result.Unclaimed{}
resp string
)
if err := c.performRequest("getunclaimed", params, resp); err != nil {
return nil, err
if err := c.performRequest("getunclaimedgas", params, &resp); err != nil {
return 0, err
}
return resp, nil
}
// GetUnspents returns UTXOs for the given NEO account.
func (c *Client) GetUnspents(address string) (*result.Unspents, error) {
var (
params = request.NewRawParams(address)
resp = &result.Unspents{}
)
if err := c.performRequest("getunspents", params, resp); err != nil {
return nil, err
i, err := strconv.ParseInt(resp, 10, 64)
if err != nil {
return 0, err
}
return resp, nil
return util.Fixed8(i), nil
}
// GetValidators returns the current NEO consensus nodes information and voting status.
@ -470,39 +416,6 @@ func (c *Client) SubmitBlock(b block.Block) error {
return nil
}
// TransferAsset sends an amount of specific asset to a given address.
// This call requires open wallet. (`wif` key in client struct.)
// If response.Result is `true` then transaction was formed correctly and was written in blockchain.
func (c *Client) TransferAsset(asset util.Uint256, address string, amount util.Fixed8) (util.Uint256, error) {
var (
err error
rawTx *transaction.Transaction
txParams = request.ContractTxParams{
AssetID: asset,
Address: address,
Value: amount,
WIF: c.WIF(),
Balancer: c.opts.Balancer,
}
resp util.Uint256
)
if rawTx, err = request.CreateRawContractTransaction(txParams); err != nil {
return resp, errors.Wrap(err, "failed to create raw transaction")
}
validUntilBlock, err := c.CalculateValidUntilBlock()
if err != nil {
return resp, errors.Wrap(err, "failed to add validUntilBlock to raw transaction")
}
rawTx.ValidUntilBlock = validUntilBlock
if err = c.SendRawTransaction(rawTx); err != nil {
return resp, errors.Wrap(err, "failed to send raw transaction")
}
return rawTx.Hash(), nil
}
// SignAndPushInvocationTx signs and pushes given script as an invocation
// transaction using given wif to sign it and spending the amount of gas
// specified. It returns a hash of the invocation transaction and an error.
@ -510,7 +423,7 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
var txHash util.Uint256
var err error
tx := transaction.NewInvocationTX(script, sysfee)
tx := transaction.New(script, sysfee)
tx.SystemFee = sysfee
validUntilBlock, err := c.CalculateValidUntilBlock()
@ -525,14 +438,6 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
}
tx.Sender = addr
gas := sysfee + netfee
if gas > 0 {
if err = request.AddInputsAndUnspentsToTx(tx, acc.Address, core.UtilityTokenID(), gas, c); err != nil {
return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction")
}
}
err = c.AddNetworkFee(tx, acc)
if err != nil {
return txHash, errors.Wrapf(err, "failed to add network fee")

View file

@ -3,7 +3,6 @@ package client
import (
"context"
"encoding/hex"
"fmt"
"net/http"
"net/http/httptest"
"strings"
@ -11,14 +10,15 @@ import (
"time"
"github.com/gorilla/websocket"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -31,113 +31,60 @@ type rpcClientTestCase struct {
check func(t *testing.T, c *Client, result interface{})
}
// getResultBlock202 returns data for block number 1 which is used by several tests.
func getResultBlock202() *result.Block {
nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22")
if err != nil {
panic(err)
}
prevBlockHash, err := util.Uint256DecodeStringLE("93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef")
if err != nil {
panic(err)
}
merkleRoot, err := util.Uint256DecodeStringLE("b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d")
if err != nil {
panic(err)
}
invScript, err := hex.DecodeString("0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996")
if err != nil {
panic(err)
}
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb")
if err != nil {
panic(err)
}
sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
txInvScript, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
if err != nil {
panic(err)
}
txVerifScript, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
if err != nil {
panic(err)
}
vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
if err != nil {
panic(err)
}
outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
tx := transaction.NewContractTX()
tx.Nonce = 3
tx.ValidUntilBlock = 1200
tx.Sender = sender
tx.Scripts = []transaction.Witness{
{
InvocationScript: txInvScript,
VerificationScript: txVerifScript,
},
}
tx.Inputs = []transaction.Input{
{
PrevHash: vin,
PrevIndex: 0,
},
}
tx.Outputs = []transaction.Output{
{
AssetID: core.GoverningTokenID(),
Amount: util.Fixed8FromInt64(99999000),
ScriptHash: outAddress,
Position: 0,
},
}
const hexB1 = "00000000e862e7907fc987cd58ddb3abb754aeb8812c9377c45e737a036fe88a622c3b8f301f2e84a86b207270830e7929530ccb841a3df7379fe6f0ac8865b33316839501cdd0847201000001000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c40ab634ce91590e77b246cee8b204e8a270268ee1ef32434cece73f425a7dbc90f1bed1dbe914edcaa2653167ad170ae10e16a9b2c6b7e0af1f711fb848fbb1b7f0c40232de6ad07ee3846bafa96302d37602349501a556df575e7df0743e45b076d6a0c6c6dd4cad3898f9e8848dd054abd303b229fd12984042f241f0e668f39a0fb0c408b4af43057df189a9d471010b5150bab442040403147c5e502bda38cde3ff8bce803f01245e07e2bfb95d57349c55dcc27e3710b82f2735d0f40eb4342908e330c40cda66f743d4ed8d856f5376953f9169581c668a9370245aef16202ebef9bb3f7f81234be62ec287d701ad7d8bf5042648019af9fe5baa0a8e05d279bfdb1d4c994130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb030057040000000000000002000000abec5362f11e75b6e02e407bb98d63675d14384100000000000000003e5f0d0000000000b00400000001abec5362f11e75b6e02e407bb98d63675d14384101590218ddf5050c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b523801fd08010c402d96d8fde4ba266f89bc71ef117361967e0d11ed84cd60942a27bc99994dc58adf36a0e74ce976aac657a87a3c19c38e8ca450c67420046b81d98c60fd8feb040c40b3c15d5d23e0403a36cf559caee2979ca6ef00fe255df0e5c3daac4da051016b41eba42668934cd3308359451bafdd5419d059179fd40859684a3b91388bf9d80c407ac048cf8540b091955a374a0f36dae560c92c0134886507a589edf58b9dfbb4e3dbd5450be34e269d2e5454eb14eb7d6280d6101b4529410f829d37634849be0c403bba4113a687ff8507c1753f8519557531cf9df51ecc20deeb2c2b003ec5a1f7588cdd50b99e40b4f8039bb56c5df7ec9e7d6ea4b02fe23792510da21c7557f394130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb0003000000abec5362f11e75b6e02e407bb98d63675d1438410000000000000000de6e0d0000000000b00400000001abec5362f11e75b6e02e407bb98d63675d143841015d0300e87648170000000c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b523801fd08010c4063fb12aa9f3fb83f6324ea9c7ec11fa9e995b51140f480409d68cf4d625e598b0632d6610602984bfa2f5e5ea9bcc62a0e6d818dd271b38530c0d1b8a71b4e0c0c4013e091eac6f304668d647c5c032fd1020597ea5204545e21c38655a6343d58492118f1231ede91af848af7e1d987d1a8816966f5fc1a7821c6c6f62734267bde0c40daadd04a7a4141d96c58de2d373e672ca071e2b82138ef52df016ac522710385db2ac73743d2fe73061fa5d6cb0ff73a7ec7f0667e4c8bff6aa0d5783128d36e0c40dab85cd87d3f92be9532292bdc6f420b0ecbf2f877c70c6a9921ee0fc900dfc53998cf020a51fa9af3d0608f6a2b9048cea3c0b586485802bbd278b261eee8a494130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"
var nonce uint64
i, err := fmt.Sscanf("0000000000000457", "%016x", &nonce)
if i != 1 {
panic("can't decode nonce")
}
const hexTxMoveNeo = "0002000000abec5362f11e75b6e02e407bb98d63675d14384100000000000000003e5f0d0000000000b00400000001abec5362f11e75b6e02e407bb98d63675d14384101590218ddf5050c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b523801fd08010c402d96d8fde4ba266f89bc71ef117361967e0d11ed84cd60942a27bc99994dc58adf36a0e74ce976aac657a87a3c19c38e8ca450c67420046b81d98c60fd8feb040c40b3c15d5d23e0403a36cf559caee2979ca6ef00fe255df0e5c3daac4da051016b41eba42668934cd3308359451bafdd5419d059179fd40859684a3b91388bf9d80c407ac048cf8540b091955a374a0f36dae560c92c0134886507a589edf58b9dfbb4e3dbd5450be34e269d2e5454eb14eb7d6280d6101b4529410f829d37634849be0c403bba4113a687ff8507c1753f8519557531cf9df51ecc20deeb2c2b003ec5a1f7588cdd50b99e40b4f8039bb56c5df7ec9e7d6ea4b02fe23792510da21c7557f394130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"
const b1Verbose = `{"id":5,"jsonrpc":"2.0","result":{"size":1681,"nextblockhash":"0xf2afe371a27c9dbac4f4a8ad8eba750898b7c04aa298e64fe9e488e947976045","confirmations":6,"hash":"0xbd178d8d4a28ec082c034f817ce2423221281a31e7e00014dbf732c4053033d2","version":0,"previousblockhash":"0x8f3b2c628ae86f037a735ec477932c81b8ae54b7abb3dd58cd87c97f90e762e8","merkleroot":"0x95831633b36588acf0e69f37f73d1a84cb0c5329790e837072206ba8842e1f30","time":1591366176001,"index":1,"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","witnesses":[{"invocation":"0c40ab634ce91590e77b246cee8b204e8a270268ee1ef32434cece73f425a7dbc90f1bed1dbe914edcaa2653167ad170ae10e16a9b2c6b7e0af1f711fb848fbb1b7f0c40232de6ad07ee3846bafa96302d37602349501a556df575e7df0743e45b076d6a0c6c6dd4cad3898f9e8848dd054abd303b229fd12984042f241f0e668f39a0fb0c408b4af43057df189a9d471010b5150bab442040403147c5e502bda38cde3ff8bce803f01245e07e2bfb95d57349c55dcc27e3710b82f2735d0f40eb4342908e330c40cda66f743d4ed8d856f5376953f9169581c668a9370245aef16202ebef9bb3f7f81234be62ec287d701ad7d8bf5042648019af9fe5baa0a8e05d279bfdb1d4c9","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"consensus_data":{"primary":0,"nonce":"0000000000000457"},"tx":[{"txid":"0x8af9ccb8e7e0f0a73e77b78dc52750e77c50f78b09ecc2f0669c0b459cc7dd89","size":575,"version":0,"nonce":2,"sender":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","sys_fee":"0","net_fee":"0.0087635","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x4138145d67638db97b402ee0b6751ef16253ecab","scopes":1}],"script":"0218ddf5050c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b5238","scripts":[{"invocation":"0c402d96d8fde4ba266f89bc71ef117361967e0d11ed84cd60942a27bc99994dc58adf36a0e74ce976aac657a87a3c19c38e8ca450c67420046b81d98c60fd8feb040c40b3c15d5d23e0403a36cf559caee2979ca6ef00fe255df0e5c3daac4da051016b41eba42668934cd3308359451bafdd5419d059179fd40859684a3b91388bf9d80c407ac048cf8540b091955a374a0f36dae560c92c0134886507a589edf58b9dfbb4e3dbd5450be34e269d2e5454eb14eb7d6280d6101b4529410f829d37634849be0c403bba4113a687ff8507c1753f8519557531cf9df51ecc20deeb2c2b003ec5a1f7588cdd50b99e40b4f8039bb56c5df7ec9e7d6ea4b02fe23792510da21c7557f3","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}]},{"txid":"0xe7cff9e4820e53232dae619a3e6f57a9430dc240b5ed7b5c0ea2cfee3e90c985","size":579,"version":0,"nonce":3,"sender":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","sys_fee":"0","net_fee":"0.0088035","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x4138145d67638db97b402ee0b6751ef16253ecab","scopes":1}],"script":"0300e87648170000000c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b5238","scripts":[{"invocation":"0c4063fb12aa9f3fb83f6324ea9c7ec11fa9e995b51140f480409d68cf4d625e598b0632d6610602984bfa2f5e5ea9bcc62a0e6d818dd271b38530c0d1b8a71b4e0c0c4013e091eac6f304668d647c5c032fd1020597ea5204545e21c38655a6343d58492118f1231ede91af848af7e1d987d1a8816966f5fc1a7821c6c6f62734267bde0c40daadd04a7a4141d96c58de2d373e672ca071e2b82138ef52df016ac522710385db2ac73743d2fe73061fa5d6cb0ff73a7ec7f0667e4c8bff6aa0d5783128d36e0c40dab85cd87d3f92be9532292bdc6f420b0ecbf2f877c70c6a9921ee0fc900dfc53998cf020a51fa9af3d0608f6a2b9048cea3c0b586485802bbd278b261eee8a4","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}]}]}}`
const hexHeader1 = "00000000e862e7907fc987cd58ddb3abb754aeb8812c9377c45e737a036fe88a622c3b8f301f2e84a86b207270830e7929530ccb841a3df7379fe6f0ac8865b33316839501cdd0847201000001000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c40ab634ce91590e77b246cee8b204e8a270268ee1ef32434cece73f425a7dbc90f1bed1dbe914edcaa2653167ad170ae10e16a9b2c6b7e0af1f711fb848fbb1b7f0c40232de6ad07ee3846bafa96302d37602349501a556df575e7df0743e45b076d6a0c6c6dd4cad3898f9e8848dd054abd303b229fd12984042f241f0e668f39a0fb0c408b4af43057df189a9d471010b5150bab442040403147c5e502bda38cde3ff8bce803f01245e07e2bfb95d57349c55dcc27e3710b82f2735d0f40eb4342908e330c40cda66f743d4ed8d856f5376953f9169581c668a9370245aef16202ebef9bb3f7f81234be62ec287d701ad7d8bf5042648019af9fe5baa0a8e05d279bfdb1d4c994130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb00"
const header1Verbose = `{"id":5,"jsonrpc":"2.0","result":{"hash":"0xbd178d8d4a28ec082c034f817ce2423221281a31e7e00014dbf732c4053033d2","size":518,"version":0,"previousblockhash":"0x8f3b2c628ae86f037a735ec477932c81b8ae54b7abb3dd58cd87c97f90e762e8","merkleroot":"0x95831633b36588acf0e69f37f73d1a84cb0c5329790e837072206ba8842e1f30","time":1591366176001,"index":1,"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","witnesses":[{"invocation":"0c40ab634ce91590e77b246cee8b204e8a270268ee1ef32434cece73f425a7dbc90f1bed1dbe914edcaa2653167ad170ae10e16a9b2c6b7e0af1f711fb848fbb1b7f0c40232de6ad07ee3846bafa96302d37602349501a556df575e7df0743e45b076d6a0c6c6dd4cad3898f9e8848dd054abd303b229fd12984042f241f0e668f39a0fb0c408b4af43057df189a9d471010b5150bab442040403147c5e502bda38cde3ff8bce803f01245e07e2bfb95d57349c55dcc27e3710b82f2735d0f40eb4342908e330c40cda66f743d4ed8d856f5376953f9169581c668a9370245aef16202ebef9bb3f7f81234be62ec287d701ad7d8bf5042648019af9fe5baa0a8e05d279bfdb1d4c9","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"confirmations":6,"nextblockhash":"0xf2afe371a27c9dbac4f4a8ad8eba750898b7c04aa298e64fe9e488e947976045"}}`
const txMoveNeoVerbose = `{"id":5,"jsonrpc":"2.0","result":{"blockhash":"0xbd178d8d4a28ec082c034f817ce2423221281a31e7e00014dbf732c4053033d2","confirmations":6,"blocktime":1591366176001,"txid":"0x8af9ccb8e7e0f0a73e77b78dc52750e77c50f78b09ecc2f0669c0b459cc7dd89","size":575,"version":0,"nonce":2,"sender":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","sys_fee":"0","net_fee":"0.0087635","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x4138145d67638db97b402ee0b6751ef16253ecab","scopes":1}],"script":"0218ddf5050c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b5238","scripts":[{"invocation":"0c402d96d8fde4ba266f89bc71ef117361967e0d11ed84cd60942a27bc99994dc58adf36a0e74ce976aac657a87a3c19c38e8ca450c67420046b81d98c60fd8feb040c40b3c15d5d23e0403a36cf559caee2979ca6ef00fe255df0e5c3daac4da051016b41eba42668934cd3308359451bafdd5419d059179fd40859684a3b91388bf9d80c407ac048cf8540b091955a374a0f36dae560c92c0134886507a589edf58b9dfbb4e3dbd5450be34e269d2e5454eb14eb7d6280d6101b4529410f829d37634849be0c403bba4113a687ff8507c1753f8519557531cf9df51ecc20deeb2c2b003ec5a1f7588cdd50b99e40b4f8039bb56c5df7ec9e7d6ea4b02fe23792510da21c7557f3","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}]}}`
// getResultBlock1 returns data for block number 1 which is used by several tests.
func getResultBlock1() *result.Block {
binB, err := hex.DecodeString(hexB1)
if err != nil {
panic(err)
}
nextCon, err := address.StringToUint160("AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL")
b := new(block.Block)
err = testserdes.DecodeBinary(binB, b)
if err != nil {
panic(err)
}
blck := &block.Block{
Base: block.Base{
Version: 0,
PrevHash: prevBlockHash,
MerkleRoot: merkleRoot,
Timestamp: 1589300496,
Index: 202,
NextConsensus: nextCon,
Script: transaction.Witness{
InvocationScript: invScript,
VerificationScript: verifScript,
},
},
ConsensusData: block.ConsensusData{
PrimaryIndex: 0,
Nonce: nonce,
},
Transactions: []*transaction.Transaction{tx},
b2Hash, err := util.Uint256DecodeStringLE("f2afe371a27c9dbac4f4a8ad8eba750898b7c04aa298e64fe9e488e947976045")
if err != nil {
panic(err)
}
// Update hashes for correct result comparison.
_ = tx.Hash()
_ = blck.Hash()
return &result.Block{
Block: blck,
Block: b,
BlockMetadata: result.BlockMetadata{
Size: 781,
Size: 1681,
NextBlockHash: &b2Hash,
Confirmations: 6,
NextBlockHash: &nextBlockHash,
},
}
}
func getTxMoveNeo() *result.TransactionOutputRaw {
b1 := getResultBlock1()
txBin, err := hex.DecodeString(hexTxMoveNeo)
if err != nil {
panic(err)
}
tx := new(transaction.Transaction)
err = testserdes.DecodeBinary(txBin, tx)
if err != nil {
panic(err)
}
return &result.TransactionOutputRaw{
Transaction: tx,
TransactionMetadata: result.TransactionMetadata{
Timestamp: b1.Timestamp,
Blockhash: b1.Block.Hash(),
Confirmations: int(b1.Confirmations),
},
}
}
@ -146,32 +93,6 @@ func getResultBlock202() *result.Block {
// published in official C# JSON-RPC API v2.10.3 reference
// (see https://docs.neo.org/docs/en-us/reference/rpc/latest-version/api.html)
var rpcClientTestCases = map[string][]rpcClientTestCase{
"getaccountstate": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
return c.GetAccountState("")
},
serverResponse: `{"jsonrpc":"2.0","id": 1,"result":{"version":0,"script_hash":"0x1179716da2e9523d153a35fb3ad10c561b1e5b1a","frozen":false,"votes":[],"balances":[{"asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","value":"94"}]}}`,
result: func(c *Client) interface{} {
scriptHash, err := util.Uint160DecodeStringLE("1179716da2e9523d153a35fb3ad10c561b1e5b1a")
if err != nil {
panic(err)
}
return &result.AccountState{
Version: 0,
ScriptHash: scriptHash,
IsFrozen: false,
Balances: result.Balances{
result.Balance{
Asset: core.GoverningTokenID(),
Value: util.Fixed8FromInt64(94),
},
},
}
},
},
},
"getapplicationlog": {
{
name: "positive",
@ -204,30 +125,6 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
},
},
},
"getassetstate": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
return c.GetAssetState(util.Uint256{})
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","type":0,"name":"NEO","amount":"100000000","available":"100000000","precision":0,"owner":"00","admin":"Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt","issuer":"AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM","expiration":4000000,"is_frozen":false}}`,
result: func(c *Client) interface{} {
return &result.AssetState{
ID: core.GoverningTokenID(),
AssetType: 0,
Name: "NEO",
Amount: util.Fixed8FromInt64(100000000),
Available: util.Fixed8FromInt64(100000000),
Precision: 0,
Owner: "00",
Admin: "Abf2qMs1pzQb8kYk9RuxtUb9jtRKJVuBJt",
Issuer: "AFmseVrdL9f9oyCzZefL9tG6UbvhPbdYzM",
Expiration: 4000000,
IsFrozen: false,
}
},
},
},
"getbestblockhash": {
{
name: "positive",
@ -248,51 +145,37 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{
name: "byIndex_positive",
invoke: func(c *Client) (interface{}, error) {
return c.GetBlockByIndex(202)
return c.GetBlockByIndex(1)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000ef9039404248180eb395d8ff59f985286b6852a68275a487e473114c4240b5938dccbfab32ee60f8b0d16144f258cc30d4f6795522fbd60109e2ed8d1423e9b810cdba5e00000000ca000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c312399694130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb02005704000000000000800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
result: func(c *Client) interface{} { return &block.Block{} },
check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*block.Block)
require.True(t, ok)
assert.Equal(t, uint32(0), res.Version)
assert.Equal(t, "cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86", res.Hash().StringLE())
assert.Equal(t, "93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef", res.PrevHash.StringLE())
assert.Equal(t, "b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d", res.MerkleRoot.StringLE())
assert.Equal(t, 1, len(res.Transactions))
assert.Equal(t, "96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581", res.Transactions[0].Hash().StringLE())
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexB1 + `"}`,
result: func(c *Client) interface{} {
b := getResultBlock1()
return b.Block
},
},
{
name: "byIndex_verbose_positive",
invoke: func(c *Client) (i interface{}, err error) {
return c.GetBlockByIndexVerbose(202)
return c.GetBlockByIndexVerbose(1)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"witnesses":[{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`,
serverResponse: b1Verbose,
result: func(c *Client) interface{} {
return getResultBlock202()
return getResultBlock1()
},
},
{
name: "byHash_positive",
invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb")
hash, err := util.Uint256DecodeStringLE("d151651e86680a7ecbc87babf3346a42e7bc9974414ce192c9c22ac4f2e9d043")
if err != nil {
panic(err)
}
return c.GetBlockByHash(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000ef9039404248180eb395d8ff59f985286b6852a68275a487e473114c4240b5938dccbfab32ee60f8b0d16144f258cc30d4f6795522fbd60109e2ed8d1423e9b810cdba5e00000000ca000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c312399694130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb02005704000000000000800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
result: func(c *Client) interface{} { return &block.Block{} },
check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*block.Block)
require.True(t, ok)
assert.Equal(t, uint32(0), res.Version)
assert.Equal(t, "cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86", res.Hash().StringLE())
assert.Equal(t, "93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef", res.PrevHash.StringLE())
assert.Equal(t, "b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d", res.MerkleRoot.StringLE())
assert.Equal(t, 1, len(res.Transactions))
assert.Equal(t, "96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581", res.Transactions[0].Hash().StringLE())
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexB1 + `"}`,
result: func(c *Client) interface{} {
b := getResultBlock1()
return b.Block
},
},
{
@ -304,9 +187,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}
return c.GetBlockByHashVerbose(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"consensus_data":{"primary":0,"nonce":"0000000000000457"},"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"witnesses":[{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"tx":[{"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}}`,
serverResponse: b1Verbose,
result: func(c *Client) interface{} {
return getResultBlock202()
return getResultBlock1()
},
},
},
@ -348,15 +231,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}
return c.GetBlockHeader(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000d039da5e49d63eb0533437d24ff8ceb6aeacf88680599c39f0ffca8948dfcdb94a3def1fca91cf45d69358414e3be77f7621e557f4cebbdb79a47d3cf56ac007f920a05e0000000001000000d60ac443bb800fb08261e75fa5925d747d48586101fd04014055041db6a59c99ab98137cc57e1e56a0a89856a311b2d2fc0aec76ec714c7616edc8fc5c9b81b27f25b7db1a61f64be0730a9cc103efcea1195cc3fe55843e264027e49c647f48bb08d3c32b79ee3432005ea577d7e497f78b46f1e81858848f961b557fb42a92e8eb4433fed203c917cbebb2138a31ed86750fb769d1e70956c0404c20054aa8bd45b520cba9410a9dd6c256481066bb657d7793fbba5551898c91b6dde81285fac841753ccfdd3193d08f19d5431313fa0d926ca965072a5fa3384026b0705078409bcc62fb98bb985edc387edeaaeba37bb7642d88a90762b2c2a62d9b61d53c097d548a368e450c4d995a178d5af28d4c93698233c52de05e3f0094534c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e4c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd624c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc24c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee6995450683073b3bb00"}`,
result: func(c *Client) interface{} { return &block.Header{} },
check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*block.Header)
require.True(t, ok)
assert.Equal(t, uint32(0), res.Version)
assert.Equal(t, "68e4bd688b852e807eef13a0ff7da7b02223e359a35153667e88f9cb4a3b0801", res.Hash().StringLE())
assert.Equal(t, "b9cddf4889cafff0399c598086f8acaeb6cef84fd2373453b03ed6495eda39d0", res.PrevHash.StringLE())
assert.Equal(t, "07c06af53c7da479dbbbcef457e521767fe73b4e415893d645cf91ca1fef3d4a", res.MerkleRoot.StringLE())
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexHeader1 + `"}`,
result: func(c *Client) interface{} {
b := getResultBlock1()
return b.Header()
},
},
{
@ -368,12 +246,12 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}
return c.GetBlockHeaderVerbose(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"hash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","size":781,"version":0,"nextblockhash":"0x13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22","previousblockhash":"0x93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef","merkleroot":"0xb8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d","time":1589300496,"index":202,"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","confirmations":6,"witnesses":[{"invocation":"0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}]}}`,
serverResponse: header1Verbose,
result: func(c *Client) interface{} {
b := getResultBlock202()
b := getResultBlock1()
return &result.Header{
Hash: b.Hash(),
Size: 781,
Size: 518,
Version: b.Version,
NextBlockHash: b.NextBlockHash,
PrevBlockHash: b.PrevHash,
@ -399,36 +277,6 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
},
},
},
"getclaimable": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
return c.GetClaimable("AGofsxAUDwt52KjaB664GYsqVAkULYvKNt")
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"claimable":[{"txid":"52ba70ef18e879785572c917795cd81422c3820b8cf44c24846a30ee7376fd77","n":1,"value":800000,"start_height":476496,"end_height":488154,"generated":746.112,"sys_fee": 3.92,"unclaimed":750.032}],"address":"AGofsxAUDwt52KjaB664GYsqVAkULYvKNt","unclaimed": 750.032}}`,
result: func(c *Client) interface{} {
txID, err := util.Uint256DecodeStringLE("52ba70ef18e879785572c917795cd81422c3820b8cf44c24846a30ee7376fd77")
if err != nil {
panic(err)
}
return &result.ClaimableInfo{
Spents: []result.Claimable{
{
Tx: txID,
N: 1,
Value: util.Fixed8FromInt64(800000),
StartHeight: 476496,
EndHeight: 488154,
Generated: util.Fixed8FromFloat(746.112),
SysFee: util.Fixed8FromFloat(3.92),
Unclaimed: util.Fixed8FromFloat(750.032),
}},
Address: "AGofsxAUDwt52KjaB664GYsqVAkULYvKNt",
Unclaimed: util.Fixed8FromFloat(750.032),
}
},
},
},
"getconnectioncount": {
{
name: "positive",
@ -593,21 +441,16 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{
name: "positive",
invoke: func(c *Client) (i interface{}, err error) {
hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96")
hash, err := util.Uint256DecodeStringLE("ca23bd5df3249836849309ca2afe972bfd288b0a7ae61302c8fd545daa8bffd6")
if err != nil {
panic(err)
}
return c.GetRawTransaction(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`,
result: func(c *Client) interface{} { return &transaction.Transaction{} },
check: func(t *testing.T, c *Client, result interface{}) {
res, ok := result.(*transaction.Transaction)
require.True(t, ok)
assert.Equal(t, uint8(0), res.Version)
assert.Equal(t, "8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96", res.Hash().StringBE())
assert.Equal(t, transaction.ContractType, res.Type)
assert.Equal(t, false, res.Trimmed)
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexTxMoveNeo + `"}`,
result: func(c *Client) interface{} {
tx := getTxMoveNeo()
return tx.Transaction
},
},
{
@ -619,68 +462,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}
return c.GetRawTransactionVerbose(hash)
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"blockhash":"0xcbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86","confirmations":8,"blocktime":1589300496,"txid":"0x96ef00d2efe03101f5b302f7fc3c8fcd22688944bdc83ab99071d77edbb08581","size":254,"type":"ContractTransaction","version":0,"nonce":3,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[{"txid":"0x33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370","vout":0}],"vout":[{"address":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","asset":"0x1a5e0e3eac2abced7de9ee2de0820a5c85e63756fcdfc29b82fead86a7c07c78","n":0,"value":"99999000"}],"scripts":[{"invocation":"0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}}`,
serverResponse: txMoveNeoVerbose,
result: func(c *Client) interface{} {
blockHash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86")
if err != nil {
panic(err)
}
sender, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
invocation, err := hex.DecodeString("0c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb")
if err != nil {
panic(err)
}
verification, err := hex.DecodeString("0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4")
if err != nil {
panic(err)
}
vin, err := util.Uint256DecodeStringLE("33e045101301854a0e07ff96a92ca1ba9b23c19501f1b7eb15ae9eea07b5f370")
if err != nil {
panic(err)
}
outAddress, err := address.StringToUint160("ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG")
if err != nil {
panic(err)
}
tx := transaction.NewContractTX()
tx.Nonce = 3
tx.ValidUntilBlock = 1200
tx.Sender = sender
tx.Scripts = []transaction.Witness{
{
InvocationScript: invocation,
VerificationScript: verification,
},
}
tx.Inputs = []transaction.Input{
{
PrevHash: vin,
PrevIndex: 0,
},
}
tx.Outputs = []transaction.Output{
{
AssetID: core.GoverningTokenID(),
Amount: util.Fixed8FromInt64(99999000),
ScriptHash: outAddress,
Position: 0,
},
}
// Update hashes for correct result comparison.
_ = tx.Hash()
return &result.TransactionOutputRaw{
Transaction: tx,
TransactionMetadata: result.TransactionMetadata{
Blockhash: blockHash,
Confirmations: 8,
Timestamp: uint64(1589300496),
},
}
return getTxMoveNeo()
},
},
},
@ -724,56 +508,15 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
},
},
},
"gettxout": {
"getunclaimedgas": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint256DecodeStringLE("f4250dab094c38d8265acc15c366dc508d2e14bf5699e12d9df26577ed74d657")
if err != nil {
panic(err)
}
return c.GetTxOut(hash, 0)
return c.GetUnclaimedGas("AGofsxAUDwt52KjaB664GYsqVAkULYvKNt")
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"N":0,"Asset":"c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","Value":"2950","Address":"AHCNSDkh2Xs66SzmyKGdoDKY752uyeXDrt"}}`,
serverResponse: `{"jsonrpc":"2.0","id":1,"result":"897299680935"}`,
result: func(c *Client) interface{} {
return &result.TransactionOutput{
N: 0,
Asset: "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
Value: util.Fixed8FromInt64(2950),
Address: "AHCNSDkh2Xs66SzmyKGdoDKY752uyeXDrt",
}
},
},
},
"getunclaimed": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
return c.GetUnclaimed("AGofsxAUDwt52KjaB664GYsqVAkULYvKNt")
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"available":750.032,"unavailable":2815.408,"unclaimed":3565.44}}`,
result: func(c *Client) interface{} {
return &result.Unclaimed{
Available: util.Fixed8FromFloat(750.032),
Unavailable: util.Fixed8FromFloat(2815.408),
Unclaimed: util.Fixed8FromFloat(3565.44),
}
},
},
},
"getunspents": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
return c.GetUnspents("AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y")
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"balance":[{"unspent":[{"txid":"0x83df8bd085fcb60b2789f7d0a9f876e5f3908567f7877fcba835e899b9dea0b5","n":0,"value":"100000000"}],"asset_hash":"0xc56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b","asset":"NEO","asset_symbol":"NEO","amount":"100000000"},{"unspent":[{"txid":"0x2ab085fa700dd0df4b73a94dc17a092ac3a85cbd965575ea1585d1668553b2f9","n":0,"value":"19351.99993"}],"asset_hash":"0x602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7","asset":"GAS","asset_symbol":"GAS","amount":"19351.99993"}],"address":"AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"}}`,
result: func(c *Client) interface{} { return &result.Unspents{} },
check: func(t *testing.T, c *Client, uns interface{}) {
res, ok := uns.(*result.Unspents)
require.True(t, ok)
assert.Equal(t, "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", res.Address)
assert.Equal(t, 2, len(res.Balance))
return util.Fixed8(897299680935)
},
},
},
@ -873,7 +616,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
return nil, c.SendRawTransaction(transaction.NewContractTX())
return nil, c.SendRawTransaction(transaction.New([]byte{byte(opcode.PUSH1)}, 0))
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":true}`,
result: func(c *Client) interface{} {
@ -994,7 +737,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{
name: "sendrawtransaction_bad_server_answer",
invoke: func(c *Client) (interface{}, error) {
return nil, c.SendRawTransaction(transaction.NewContractTX())
return nil, c.SendRawTransaction(transaction.New([]byte{byte(opcode.PUSH1)}, 0))
},
},
{
@ -1015,24 +758,12 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
},
},
`{"id":1,"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params"}}`: {
{
name: "getaccountstate_invalid_params_error",
invoke: func(c *Client) (i interface{}, err error) {
return c.GetAccountState("")
},
},
{
name: "getapplicationlog_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetApplicationLog(util.Uint256{})
},
},
{
name: "getassetstate_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetAssetState(core.GoverningTokenID())
},
},
{
name: "getbestblockhash_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
@ -1087,12 +818,6 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
return c.GetBlockSysFee(1)
},
},
{
name: "getclaimable_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetClaimable("")
},
},
{
name: "getconnectioncount_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
@ -1142,21 +867,9 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
},
},
{
name: "gettxoutput_invalid_params_error",
name: "getunclaimedgas_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetTxOut(util.Uint256{}, 0)
},
},
{
name: "getunclaimed_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetUnclaimed("")
},
},
{
name: "getunspents_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetUnspents("")
return c.GetUnclaimedGas("")
},
},
{
@ -1191,24 +904,12 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
},
},
`{}`: {
{
name: "getaccountstate_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetAccountState("")
},
},
{
name: "getapplicationlog_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetApplicationLog(util.Uint256{})
},
},
{
name: "getassetstate_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetAssetState(core.GoverningTokenID())
},
},
{
name: "getbestblockhash_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
@ -1269,12 +970,6 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
return c.GetBlockSysFee(1)
},
},
{
name: "getclaimable_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetClaimable("")
},
},
{
name: "getconnectioncount_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
@ -1336,21 +1031,9 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
},
},
{
name: "getxoutput_unmarshalling_error",
name: "getunclaimedgas_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetTxOut(util.Uint256{}, 0)
},
},
{
name: "getunclaimed_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetUnclaimed("")
},
},
{
name: "getunspents_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetUnspents("")
return c.GetUnclaimedGas("")
},
},
{
@ -1380,7 +1063,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{
name: "sendrawtransaction_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return nil, c.SendRawTransaction(transaction.NewContractTX())
return nil, c.SendRawTransaction(transaction.New([]byte{byte(opcode.PUSH1)}, 0))
},
},
{

View file

@ -117,8 +117,8 @@ func TestWSClientEvents(t *testing.T) {
var events = []string{
`{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","executions":[{"trigger":"Application","contract":"0x0000000000000000000000000000000000000000","vmstate":"HALT","gas_consumed":"2.291","stack":[],"notifications":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"636f6e74726163742063616c6c"},{"type":"ByteArray","value":"7472616e73666572"},{"type":"Array","value":[{"type":"ByteArray","value":"769162241eedf97c2481652adf1ba0f5bf57431b"},{"type":"ByteArray","value":"316e851039019d39dfc2c37d6c3fee19fd580987"},{"type":"Integer","value":"1000"}]}]}},{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"7472616e73666572"},{"type":"ByteArray","value":"769162241eedf97c2481652adf1ba0f5bf57431b"},{"type":"ByteArray","value":"316e851039019d39dfc2c37d6c3fee19fd580987"},{"type":"Integer","value":"1000"}]}}]}]}]}`,
`{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","state":{"type":"Array","value":[{"type":"ByteArray","value":"636f6e74726163742063616c6c"},{"type":"ByteArray","value":"7472616e73666572"},{"type":"Array","value":[{"type":"ByteArray","value":"769162241eedf97c2481652adf1ba0f5bf57431b"},{"type":"ByteArray","value":"316e851039019d39dfc2c37d6c3fee19fd580987"},{"type":"Integer","value":"1000"}]}]}}]}`,
`{"jsonrpc":"2.0","method":"transaction_added","params":[{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","size":277,"type":"InvocationTransaction","version":1,"nonce":9,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0037721","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x870958fd19ee3f6c7dc3c2df399d013910856e31","scopes":1}],"vin":[],"vout":[],"scripts":[{"invocation":"0c4027727296b84853c5d9e07fb8a40e885246ae25641383b16eefbe92027ecb1635b794aacf6bbfc3e828c73829b14791c483d19eb758b57638e3191393dbf2d288","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}],"script":"01e8030c14316e851039019d39dfc2c37d6c3fee19fd5809870c14769162241eedf97c2481652adf1ba0f5bf57431b13c00c087472616e736665720c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b5238"}]}`,
`{"jsonrpc":"2.0","method":"block_added","params":[{"hash":"0x239fea00c54c2f6812612874183b72bef4473fcdf68bf8da08d74fd5b6cab030","version":0,"previousblockhash":"0x04f7580b111ec75f0ce68d3a9fd70a0544b4521b4a98541694d8575c548b759e","merkleroot":"0xb2c7230ebee4cb83bc03afadbba413e6bca8fcdeaf9c077bea060918da0e52a1","time":1590006200,"index":207,"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","witnesses":[{"invocation":"0c4063429fca5ff75c964d9e38179c75978e33f8174d91a780c2e825265cf2447281594afdd5f3e216dcaf5ff0693aec83f415996cf224454495495f6bd0a4c5d08f0c4099680903a954278580d8533121c2cd3e53a089817b6a784901ec06178a60b5f1da6e70422bdcadc89029767e08d66ce4180b99334cb2d42f42e4216394af15920c4067d5e362189e48839a24e187c59d46f5d9db862c8a029777f1548b19632bfdc73ad373827ed02369f925e89c2303b64e6b9838dca229949b9b9d3bd4c0c3ed8f0c4021d4c00d4522805883f1db929554441bcbbee127c48f6b7feeeb69a72a78c7f0a75011663e239c0820ef903f36168f42936de10f0ef20681cb735a4b53d0390f","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"consensus_data":{"primary":0,"nonce":"0000000000000457"},"tx":[{"txid":"0xf736cd91ab84062a21a09b424346b241987f6245ffe8c2b2db39d595c3c222f7","size":204,"type":"InvocationTransaction","version":1,"nonce":8,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0030421","valid_until_block":1200,"attributes":[],"cosigners":[],"vin":[],"vout":[],"scripts":[{"invocation":"0c4016e7a112742409cdfaad89dcdbcb52c94c5c1a69dfe5d8b999649eaaa787e31ca496d1734d6ea606c749ad36e9a88892240ae59e0efa7f544e0692124898d512","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}],"script":"10c00c04696e69740c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b52"},{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","size":277,"type":"InvocationTransaction","version":1,"nonce":9,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0037721","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x870958fd19ee3f6c7dc3c2df399d013910856e31","scopes":1}],"vin":[],"vout":[],"scripts":[{"invocation":"0c4027727296b84853c5d9e07fb8a40e885246ae25641383b16eefbe92027ecb1635b794aacf6bbfc3e828c73829b14791c483d19eb758b57638e3191393dbf2d288","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}],"script":"01e8030c14316e851039019d39dfc2c37d6c3fee19fd5809870c14769162241eedf97c2481652adf1ba0f5bf57431b13c00c087472616e736665720c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b5238"}]}]}`,
`{"jsonrpc":"2.0","method":"transaction_added","params":[{"txid":"0x1c615d4043c98fc0e285c2f40cc3601cf4ebe1cf9d2b404dfc67c9cd085444ec","size":265,"version":0,"nonce":9,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0036521","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x870958fd19ee3f6c7dc3c2df399d013910856e31","scopes":1}],"script":"007b0c1420728274afafc36f43a071d328cfa3e629d9cbb00c14316e851039019d39dfc2c37d6c3fee19fd58098713c00c087472616e736665720c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b5238","scripts":[{"invocation":"0c4034d02f3b97a220ffe79640e482b887ec0e44dcc95e719f5e2b43b29987f0c9822b9af0499d90094c6ad3ba191e434a3df5dd378d3b73318cf47c9f2d6d801cc8","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}`,
`{"jsonrpc":"2.0","method":"block_added","params":[{"hash":"0x765ea65b4de6addfee29b1c90ac922d1901c8d7ab7f2366da9a8ad3dd71ca703","version":0,"previousblockhash":"0xbdeed527a43ab72d5d8cecf1dc6ee142112ff8a8eaaaebc7206d3df3bf3c1169","merkleroot":"0xa1b321f59b127cddd23b0cd47fc9ec7920647d30d7ab23318a106597b9c9abad","time":1591366176006,"index":6,"nextconsensus":"AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL","witnesses":[{"invocation":"0c40da1f4b546a8a60e96596351234d7709391866bb3590a290133bc0c45837f1dac6351ee32506a7e0bbf6fcbcc3ec01222ccfe84bc1d4071221f4c432ebf569b620c40ee5906328012a8a4a411e7fa23aa8ba21fedb81b11581e5a287cad961fa36d2a20b2069549a5a14860d9e9ae3640ea20f9191d60ab7c2aeddf43edd6dabe558c0c40f5391e79e7d62f7ccaa900511d530f89de183fa51bc4af744bda81f763e14ddd7fb953e69b0901660d4752f240d5269344d0b64b50b124d1a316ad72486da15e0c40012f773faef2aee4af59e083b443ebe6cf404d12f49d32966c5f48f2c203e284429615aa2d34c827356d55c3be1612f67a5b725f6ff49b9b95b1f60306a72b71","verification":"130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"}],"consensus_data":{"primary":0,"nonce":"0000000000000457"},"tx":[{"txid":"0x1c615d4043c98fc0e285c2f40cc3601cf4ebe1cf9d2b404dfc67c9cd085444ec","size":265,"version":0,"nonce":9,"sender":"ALHF9wsXZVEuCGgmDA6ZNsCLtrb4A1g4yG","sys_fee":"0","net_fee":"0.0036521","valid_until_block":1200,"attributes":[],"cosigners":[{"account":"0x870958fd19ee3f6c7dc3c2df399d013910856e31","scopes":1}],"script":"007b0c1420728274afafc36f43a071d328cfa3e629d9cbb00c14316e851039019d39dfc2c37d6c3fee19fd58098713c00c087472616e736665720c14769162241eedf97c2481652adf1ba0f5bf57431b41627d5b5238","scripts":[{"invocation":"0c4034d02f3b97a220ffe79640e482b887ec0e44dcc95e719f5e2b43b29987f0c9822b9af0499d90094c6ad3ba191e434a3df5dd378d3b73318cf47c9f2d6d801cc8","verification":"0c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}]}]}]}`,
`{"jsonrpc":"2.0","method":"event_missed","params":[]}`,
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

View file

@ -5,77 +5,14 @@ import (
"fmt"
"strconv"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/wallet"
errs "github.com/pkg/errors"
)
// CreateRawContractTransaction returns contract-type Transaction built from specified parameters.
func CreateRawContractTransaction(params ContractTxParams) (*transaction.Transaction, error) {
var (
err error
tx = transaction.NewContractTX()
toAddressHash, fromAddressHash util.Uint160
fromAddress string
receiverOutput *transaction.Output
wif, assetID, toAddress, amount, balancer = params.WIF, params.AssetID, params.Address, params.Value, params.Balancer
)
fromAddress = wif.PrivateKey.Address()
if fromAddressHash, err = address.StringToUint160(fromAddress); err != nil {
return nil, errs.Wrapf(err, "Failed to take script hash from address: %v", fromAddress)
}
if toAddressHash, err = address.StringToUint160(toAddress); err != nil {
return nil, errs.Wrapf(err, "Failed to take script hash from address: %v", toAddress)
}
tx.Sender = fromAddressHash
if err = AddInputsAndUnspentsToTx(tx, fromAddress, assetID, amount, balancer); err != nil {
return nil, errs.Wrap(err, "failed to add inputs and unspents to transaction")
}
receiverOutput = transaction.NewOutput(assetID, amount, toAddressHash)
tx.AddOutput(receiverOutput)
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 tx, nil
}
// AddInputsAndUnspentsToTx adds inputs needed to transaction and one output
// with change.
func AddInputsAndUnspentsToTx(tx *transaction.Transaction, addr string, assetID util.Uint256, amount util.Fixed8, balancer BalanceGetter) error {
scriptHash, err := address.StringToUint160(addr)
if err != nil {
return errs.Wrapf(err, "failed to take script hash from address: %v", addr)
}
inputs, spent, err := balancer.CalculateInputs(addr, assetID, amount)
if err != nil {
return errs.Wrap(err, "failed to get inputs")
}
for _, input := range inputs {
tx.AddInput(&input)
}
if senderUnspent := spent - amount; senderUnspent > 0 {
senderOutput := transaction.NewOutput(assetID, senderUnspent, scriptHash)
tx.AddOutput(senderOutput)
}
return nil
}
// DetailsToSCProperties extract the fields needed from ContractDetails
// and converts them to smartcontract.PropertyState.
func DetailsToSCProperties(contract *smartcontract.ContractDetails) smartcontract.PropertyState {

View file

@ -1,40 +0,0 @@
package request
/*
Definition of types, interfaces and variables
required for raw transaction composing.
*/
import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
)
type (
// ContractTxParams contains parameters for tx to transfer assets.
// It includes (*Client).TransferAsset call params and utility data.
ContractTxParams struct {
AssetID util.Uint256
Address string
Value util.Fixed8
WIF keys.WIF // a WIF to send the transaction
// since there are many ways to provide unspents,
// transaction composer stays agnostic to that how
// unspents was got;
Balancer BalanceGetter
}
// BalanceGetter is an interface supporting CalculateInputs() method.
BalanceGetter interface {
// parameters
// address: base58-encoded address assets would be transferred from
// assetID: asset identifier
// amount: an asset amount to spend
// return values
// inputs: UTXO's for the preparing transaction
// total: summarized asset amount from all the `inputs`
// error: error would be considered in the caller function
CalculateInputs(address string, assetID util.Uint256, amount util.Fixed8) (inputs []transaction.Input, total util.Fixed8, err error)
}
)

View file

@ -1,51 +0,0 @@
package result
import (
"bytes"
"sort"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// AccountState wrapper used for the representation of
// state.Account on the RPC Server.
type AccountState struct {
Version uint8 `json:"version"`
ScriptHash util.Uint160 `json:"script_hash"`
IsFrozen bool `json:"frozen"`
Balances []Balance `json:"balances"`
}
// Balances type for sorting balances in rpc response.
type Balances []Balance
func (b Balances) Len() int { return len(b) }
func (b Balances) Less(i, j int) bool { return bytes.Compare(b[i].Asset[:], b[j].Asset[:]) != -1 }
func (b Balances) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
// Balance response wrapper.
type Balance struct {
Asset util.Uint256 `json:"asset"`
Value util.Fixed8 `json:"value"`
}
// NewAccountState creates a new Account wrapper.
func NewAccountState(a *state.Account) AccountState {
balances := make(Balances, 0, len(a.Balances))
for k, v := range a.GetBalanceValues() {
balances = append(balances, Balance{
Asset: k,
Value: v,
})
}
sort.Sort(balances)
return AccountState{
Version: a.Version,
ScriptHash: a.ScriptHash,
IsFrozen: a.IsFrozen,
Balances: balances,
}
}

View file

@ -1,41 +0,0 @@
package result
import (
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// AssetState wrapper used for the representation of
// state.Asset on the RPC Server.
type AssetState struct {
ID util.Uint256 `json:"id"`
AssetType transaction.AssetType `json:"type"`
Name string `json:"name"`
Amount util.Fixed8 `json:"amount"`
Available util.Fixed8 `json:"available"`
Precision uint8 `json:"precision"`
Owner string `json:"owner"`
Admin string `json:"admin"`
Issuer string `json:"issuer"`
Expiration uint32 `json:"expiration"`
IsFrozen bool `json:"is_frozen"`
}
// NewAssetState creates a new Asset wrapper.
func NewAssetState(a *state.Asset) AssetState {
return AssetState{
ID: a.ID,
AssetType: a.AssetType,
Name: a.GetName(),
Amount: a.Amount,
Available: a.Available,
Precision: a.Precision,
Owner: a.Owner.String(),
Admin: address.Uint160ToString(a.Admin),
Issuer: address.Uint160ToString(a.Issuer),
Expiration: a.Expiration,
IsFrozen: a.IsFrozen,
}
}

View file

@ -33,7 +33,7 @@ func NewBlock(b *block.Block, chain blockchainer.Blockchainer) Block {
Block: b,
BlockMetadata: BlockMetadata{
Size: io.GetVarSize(b),
Confirmations: chain.BlockHeight() - b.Index - 1,
Confirmations: chain.BlockHeight() - b.Index + 1,
},
}

View file

@ -1,22 +0,0 @@
package result
import "github.com/nspcc-dev/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"`
}

View file

@ -1,27 +0,0 @@
package result
import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// TransactionOutput is a wrapper to represent transaction's output.
type TransactionOutput struct {
N int `json:"n"`
Asset string `json:"asset"`
Value util.Fixed8 `json:"value"`
Address string `json:"address"`
}
// NewTxOutput converts out to a TransactionOutput.
func NewTxOutput(out *transaction.Output) *TransactionOutput {
addr := address.Uint160ToString(out.ScriptHash)
return &TransactionOutput{
N: out.Position,
Asset: "0x" + out.AssetID.String(),
Value: out.Amount,
Address: addr,
}
}

View file

@ -28,10 +28,6 @@ type TransactionMetadata struct {
func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header, chain blockchainer.Blockchainer) TransactionOutputRaw {
// confirmations formula
confirmations := int(chain.BlockHeight() - header.Base.Index + 1)
// set index position
for i, o := range tx.Outputs {
o.Position = i
}
return TransactionOutputRaw{
Transaction: tx,
TransactionMetadata: TransactionMetadata{

Some files were not shown because too many files have changed in this diff Show more