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 - ../config/protocol.privnet.docker.single.yml:/config/protocol.privnet.yml
- ./wallets/wallet1.json:/wallet1.json - ./wallets/wallet1.json:/wallet1.json
- volume_chain:/chains - volume_chain:/chains
- ./1600-privnet-blocks-single.acc.gz:/privnet-blocks.acc.gz
networks: networks:
neo_go_network: neo_go_network:
ipv4_address: 172.200.0.1 ipv4_address: 172.200.0.1

View file

@ -30,8 +30,6 @@ LABEL version=$VERSION
WORKDIR / WORKDIR /
COPY --from=builder /neo-go/config /config 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 /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 /go/bin/neo-go /usr/bin/neo-go
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 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) { func printTxInfo(t *transaction.Transaction) {
fmt.Printf("Hash: %s\n", t.Hash().StringLE()) 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 package wallet
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "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/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/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -82,6 +85,7 @@ func newNEP5Commands() []cli.Command {
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
rpcFlag, rpcFlag,
outFlag,
timeoutFlag, timeoutFlag,
fromAddrFlag, fromAddrFlag,
toAddrFlag, toAddrFlag,
@ -341,11 +345,30 @@ func transferNEP5(ctx *cli.Context) error {
return cli.NewExitError(err, 1) 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 { if err != nil {
return cli.NewExitError(err, 1) 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 return nil
} }

View file

@ -4,22 +4,16 @@ import (
"bufio" "bufio"
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"strings" "strings"
"syscall" "syscall"
"github.com/nspcc-dev/neo-go/cli/flags" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "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/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/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -179,29 +173,6 @@ func NewCommands() []cli.Command {
forceFlag, 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", Name: "multisig",
Usage: "work with multisig address", Usage: "work with multisig address",
@ -247,46 +218,18 @@ func claimGas(ctx *cli.Context) error {
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
info, err := c.GetClaimable(addrFlag.String()) // Temporary.
if err != nil { neoHash, err := util.Uint160DecodeStringLE("3b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c")
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()
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
tx.ValidUntilBlock = validUntilBlock
tx.Sender = scriptHash
tx.AddOutput(&transaction.Output{ hash, err := c.TransferNEP5(acc, scriptHash, neoHash, 0, 0)
AssetID: core.UtilityTokenID(),
Amount: info.Unclaimed,
ScriptHash: scriptHash,
})
err = c.AddNetworkFee(tx, acc)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) 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 return nil
} }
@ -477,99 +420,6 @@ func askForConsent() bool {
return false 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()) { func getGoContext(ctx *cli.Context) (context.Context, func()) {
if dur := ctx.Duration("timeout"); dur != 0 { if dur := ctx.Duration("timeout"); dur != 0 {
return context.WithTimeout(context.Background(), dur) return context.WithTimeout(context.Background(), dur)
@ -659,18 +509,6 @@ func openWallet(path string) (*wallet.Wallet, error) {
return wallet.NewWalletFromFile(path) return wallet.NewWalletFromFile(path)
} }
func getAssetID(s string) (util.Uint256, error) {
s = strings.ToLower(s)
switch {
case s == "neo":
return core.GoverningTokenID(), nil
case s == "gas":
return core.UtilityTokenID(), nil
default:
return util.Uint256DecodeStringLE(s)
}
}
func newAccountFromWIF(wif string) (*wallet.Account, error) { func newAccountFromWIF(wif string) (*wallet.Account, error) {
// note: NEP2 strings always have length of 58 even though // note: NEP2 strings always have length of 58 even though
// base58 strings can have different lengths even if slice lengths are equal // base58 strings can have different lengths even if slice lengths are equal

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,16 +34,13 @@ which would yield the response:
| Method | | Method |
| ------- | | ------- |
| `getaccountstate` |
| `getapplicationlog` | | `getapplicationlog` |
| `getassetstate` |
| `getbestblockhash` | | `getbestblockhash` |
| `getblock` | | `getblock` |
| `getblockcount` | | `getblockcount` |
| `getblockhash` | | `getblockhash` |
| `getblockheader` | | `getblockheader` |
| `getblocksysfee` | | `getblocksysfee` |
| `getclaimable` |
| `getconnectioncount` | | `getconnectioncount` |
| `getcontractstate` | | `getcontractstate` |
| `getnep5balances` | | `getnep5balances` |
@ -53,9 +50,7 @@ which would yield the response:
| `getrawtransaction` | | `getrawtransaction` |
| `getstorage` | | `getstorage` |
| `gettransactionheight` | | `gettransactionheight` |
| `gettxout` | | `getunclaimedgas` |
| `getunclaimed` |
| `getunspents` |
| `getvalidators` | | `getvalidators` |
| `getversion` | | `getversion` |
| `invoke` | | `invoke` |
@ -77,6 +72,11 @@ in returning it.
Both methods also don't currently support arrays in function parameters. 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 ### Unsupported methods
Methods listed down below are not going to be supported for various reasons 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 | | `claimgas` | Doesn't fit neo-go wallet model, use CLI to do that |
| `dumpprivkey` | Shouldn't exist for security reasons, see `claimgas` comment also | | `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 | | `getmetricblocktimestamp` | Not really useful, use other means for node monitoring |
| `getnewaddress` | See `claimgas` comment | | `getnewaddress` | See `claimgas` comment |
| `getunclaimedgas` | Use `getunclaimed` instead, see `claimgas` comment also |
| `getwalletheight` | Not applicable to neo-go, see `claimgas` comment | | `getwalletheight` | Not applicable to neo-go, see `claimgas` comment |
| `importprivkey` | 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 | | `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) fromAddressHash, err := address.StringToUint160(fromAddress)
require.NoError(t, err) require.NoError(t, err)
tx := transaction.NewInvocationTX([]byte{0x51}, 1) tx := transaction.New([]byte{0x51}, 1)
tx.Version = 0 tx.Version = 0
tx.Sender = fromAddressHash tx.Sender = fromAddressHash
tx.Attributes = append(tx.Attributes, tx.Attributes = append(tx.Attributes,

View file

@ -40,7 +40,6 @@ var syscalls = map[string]map[string]string{
}, },
"blockchain": { "blockchain": {
"GetAccount": "Neo.Blockchain.GetAccount", "GetAccount": "Neo.Blockchain.GetAccount",
"GetAsset": "Neo.Blockchain.GetAsset",
"GetBlock": "Neo.Blockchain.GetBlock", "GetBlock": "Neo.Blockchain.GetBlock",
"GetContract": "Neo.Blockchain.GetContract", "GetContract": "Neo.Blockchain.GetContract",
"GetHeader": "Neo.Blockchain.GetHeader", "GetHeader": "Neo.Blockchain.GetHeader",
@ -67,26 +66,8 @@ var syscalls = map[string]map[string]string{
"transaction": { "transaction": {
"GetAttributes": "Neo.Transaction.GetAttributes", "GetAttributes": "Neo.Transaction.GetAttributes",
"GetHash": "Neo.Transaction.GetHash", "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", "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",
},
"contract": { "contract": {
"GetScript": "Neo.Contract.GetScript", "GetScript": "Neo.Contract.GetScript",
"IsPayable": "Neo.Contract.IsPayable", "IsPayable": "Neo.Contract.IsPayable",
@ -95,15 +76,6 @@ var syscalls = map[string]map[string]string{
"Migrate": "Neo.Contract.Migrate", "Migrate": "Neo.Contract.Migrate",
"GetStorageContext": "Neo.Contract.GetStorageContext", "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": { "engine": {
"GetScriptContainer": "System.ExecutionEngine.GetScriptContainer", "GetScriptContainer": "System.ExecutionEngine.GetScriptContainer",
"GetCallingScriptHash": "System.ExecutionEngine.GetCallingScriptHash", "GetCallingScriptHash": "System.ExecutionEngine.GetCallingScriptHash",

View file

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

View file

@ -1,7 +1,6 @@
package config package config
import ( import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -38,19 +37,12 @@ type (
SecondsPerBlock int `yaml:"SecondsPerBlock"` SecondsPerBlock int `yaml:"SecondsPerBlock"`
SeedList []string `yaml:"SeedList"` SeedList []string `yaml:"SeedList"`
StandbyValidators []string `yaml:"StandbyValidators"` StandbyValidators []string `yaml:"StandbyValidators"`
SystemFee SystemFee `yaml:"SystemFee"`
// Whether to verify received blocks. // Whether to verify received blocks.
VerifyBlocks bool `yaml:"VerifyBlocks"` VerifyBlocks bool `yaml:"VerifyBlocks"`
// Whether to verify transactions in received blocks. // Whether to verify transactions in received blocks.
VerifyTransactions bool `yaml:"VerifyTransactions"` 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 describes the mode the blockchain will operate on.
NetMode uint32 NetMode uint32
) )
@ -70,15 +62,3 @@ func (n NetMode) String() string {
return "net unknown" 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/dbft/crypto"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -44,7 +45,7 @@ func TestNeoBlock_Setters(t *testing.T) {
b.Block.PrevHash = util.Uint256{9, 8, 7} b.Block.PrevHash = util.Uint256{9, 8, 7}
require.Equal(t, util.Uint256{9, 8, 7}, b.PrevHash()) 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) b.SetTransactions(txx)
require.Equal(t, txx, b.Transactions()) require.Equal(t, txx, b.Transactions())
} }

View file

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

View file

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

View file

@ -160,6 +160,9 @@ func (b *Block) DecodeBinary(br *io.BinReader) {
txes[i] = tx txes[i] = tx
} }
b.Transactions = txes b.Transactions = txes
if br.Err != nil {
return
}
br.Err = b.Verify() br.Err = b.Verify()
} }
@ -227,5 +230,8 @@ func (b *Block) UnmarshalJSON(data []byte) error {
b.Base = *base b.Base = *base
b.Transactions = auxb.Transactions b.Transactions = auxb.Transactions
b.ConsensusData = auxb.ConsensusData b.ConsensusData = auxb.ConsensusData
// Some tests rely on hash presence and we're usually precomputing
// other hashes upon deserialization.
_ = b.ConsensusData.Hash()
return nil 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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -84,7 +85,7 @@ func newDumbBlock() *Block {
Nonce: 1111, Nonce: 1111,
}, },
Transactions: []*transaction.Transaction{ Transactions: []*transaction.Transaction{
transaction.NewIssueTX(), transaction.New([]byte{byte(opcode.PUSH1)}, 0),
}, },
} }
} }

View file

@ -2,7 +2,6 @@ package core
import ( import (
"fmt" "fmt"
"math"
"math/big" "math/big"
"sort" "sort"
"sync" "sync"
@ -33,13 +32,6 @@ const (
headerBatchCount = 2000 headerBatchCount = 2000
version = "0.1.0" 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 defaultMemPoolSize = 50000
) )
@ -370,7 +362,6 @@ func (bc *Blockchain) notificationDispatcher() {
if len(txFeed) != 0 || len(notificationFeed) != 0 || len(executionFeed) != 0 { if len(txFeed) != 0 || len(notificationFeed) != 0 || len(executionFeed) != 0 {
var aerIdx int var aerIdx int
for _, tx := range event.block.Transactions { for _, tx := range event.block.Transactions {
if tx.Type == transaction.InvocationType {
aer := event.appExecResults[aerIdx] aer := event.appExecResults[aerIdx]
if !aer.TxHash.Equals(tx.Hash()) { if !aer.TxHash.Equals(tx.Hash()) {
panic("inconsistent application execution results") panic("inconsistent application execution results")
@ -386,7 +377,6 @@ func (bc *Blockchain) notificationDispatcher() {
} }
} }
} }
}
for ch := range txFeed { for ch := range txFeed {
ch <- tx ch <- tx
} }
@ -536,7 +526,6 @@ func (bc *Blockchain) processHeader(h *block.Header, batch storage.Batch, header
} }
buf.Reset() buf.Reset()
buf.BinWriter.WriteU32LE(0) // sys fee is yet to be calculated
h.EncodeBinary(buf.BinWriter) h.EncodeBinary(buf.BinWriter)
if buf.Err != nil { if buf.Err != nil {
return buf.Err return buf.Err
@ -549,13 +538,6 @@ func (bc *Blockchain) processHeader(h *block.Header, batch storage.Batch, header
return nil 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 // TODO: storeBlock needs some more love, its implemented as in the original
// project. This for the sake of development speed and understanding of what // project. This for the sake of development speed and understanding of what
// is happening here, quite allot as you can see :). If things are wired together // is happening here, quite allot as you can see :). If things are wired together
@ -563,11 +545,7 @@ func (bc *Blockchain) getSystemFeeAmount(h util.Uint256) uint32 {
func (bc *Blockchain) storeBlock(block *block.Block) error { func (bc *Blockchain) storeBlock(block *block.Block) error {
cache := dao.NewCached(bc.dao) cache := dao.NewCached(bc.dao)
appExecResults := make([]*state.AppExecResult, 0, len(block.Transactions)) appExecResults := make([]*state.AppExecResult, 0, len(block.Transactions))
fee := bc.getSystemFeeAmount(block.PrevHash) if err := cache.StoreAsBlock(block); err != nil {
for _, tx := range block.Transactions {
fee += uint32(tx.SystemFee.IntegralValue())
}
if err := cache.StoreAsBlock(block, fee); err != nil {
return err return err
} }
@ -580,162 +558,12 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return err return err
} }
if err := cache.PutUnspentCoinState(tx.Hash(), state.NewUnspentCoin(block.Index, tx)); err != nil {
return err
}
// 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)
if err != nil {
return err
}
for _, input := range inputs {
if len(unspent.States) <= int(input.PrevIndex) {
return fmt.Errorf("bad input: %s/%d", input.PrevHash.StringLE(), input.PrevIndex)
}
if unspent.States[input.PrevIndex].State&state.CoinSpent != 0 {
return fmt.Errorf("double spend: %s/%d", input.PrevHash.StringLE(), input.PrevIndex)
}
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) systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx)
v := SpawnVM(systemInterop) v := SpawnVM(systemInterop)
v.LoadScript(t.Script) v.LoadScript(tx.Script)
v.SetPriceGetter(getPrice) v.SetPriceGetter(getPrice)
if bc.config.FreeGasLimit > 0 { if bc.config.FreeGasLimit > 0 {
v.SetGasLimit(bc.config.FreeGasLimit + t.Gas) v.SetGasLimit(bc.config.FreeGasLimit + tx.SystemFee)
} }
err := v.Run() err := v.Run()
@ -801,7 +629,6 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return errors.Wrap(err, "failed to Store notifications") return errors.Wrap(err, "failed to Store notifications")
} }
} }
}
for i := range bc.contracts.Contracts { for i := range bc.contracts.Contracts {
systemInterop := bc.newInteropContext(trigger.Application, cache, block, nil) systemInterop := bc.newInteropContext(trigger.Application, cache, block, nil)
@ -927,7 +754,22 @@ func (bc *Blockchain) GetNEP5Balances(acc util.Uint160) *state.NEP5Balances {
// GetUtilityTokenBalance returns utility token (GAS) balance for the acc. // GetUtilityTokenBalance returns utility token (GAS) balance for the acc.
func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) util.Fixed8 { 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. // LastBatch returns last persisted storage batch.
@ -935,25 +777,6 @@ func (bc *Blockchain) LastBatch() *storage.MemBatch {
return bc.lastBatch 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. // persist flushes current in-memory Store contents to the persistent storage.
func (bc *Blockchain) persist() error { func (bc *Blockchain) persist() error {
var ( var (
@ -1002,7 +825,7 @@ func (bc *Blockchain) headerListLen() (n int) {
// GetTransaction returns a TX and its height by the given hash. // GetTransaction returns a TX and its height by the given hash.
func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) { 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 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) 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 { if err != nil {
return nil, err return nil, err
} }
@ -1055,7 +878,7 @@ func (bc *Blockchain) GetHeader(hash util.Uint256) (*block.Header, error) {
return tb.Header(), nil return tb.Header(), nil
} }
} }
block, _, err := bc.dao.GetBlock(hash) block, err := bc.dao.GetBlock(hash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1115,17 +938,6 @@ func (bc *Blockchain) HeaderHeight() uint32 {
return uint32(bc.headerListLen() - 1) 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. // GetContractState returns contract by its script hash.
func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract { func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract {
contract, err := bc.dao.GetContractState(hash) contract, err := bc.dao.GetContractState(hash)
@ -1144,15 +956,6 @@ func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *state.Account {
return as 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. // GetConfig returns the config stored in the blockchain.
func (bc *Blockchain) GetConfig() config.ProtocolConfiguration { func (bc *Blockchain) GetConfig() config.ProtocolConfiguration {
return bc.config return bc.config
@ -1220,10 +1023,11 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult)
bc.unsubCh <- ch bc.unsubCh <- ch
} }
// CalculateClaimable calculates the amount of GAS which can be claimed for a transaction with value. // CalculateClaimable calculates the amount of GAS generated by owning specified
// First return value is GAS generated between startHeight and endHeight. // amount of NEO between specified blocks. The amount of NEO being passed is in
// Second return value is GAS returned from accumulated SystemFees between startHeight and endHeight. // its natural non-divisible form (1 NEO as 1, 2 NEO as 2, no multiplication by
func (bc *Blockchain) CalculateClaimable(value util.Fixed8, startHeight, endHeight uint32) (util.Fixed8, util.Fixed8, error) { // 10⁸ is neeeded as for Fixed8).
func (bc *Blockchain) CalculateClaimable(value int64, startHeight, endHeight uint32) util.Fixed8 {
var amount util.Fixed8 var amount util.Fixed8
di := uint32(bc.decrementInterval) 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]) amount += util.Fixed8(iend-istart) * util.Fixed8(bc.generationAmount[ustart])
} }
if startHeight == 0 { return amount * util.Fixed8(value)
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
} }
// FeePerByte returns transaction network fee per byte. // 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 // ApplyPolicyToTxSet applies configured policies to given transaction set. It
// expects slice to be ordered by fee and returns a subslice of 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 { if bc.config.MaxTransactionsPerBlock != 0 && len(txes) > bc.config.MaxTransactionsPerBlock {
txes = txes[:bc.config.MaxTransactionsPerBlock] txes = txes[:bc.config.MaxTransactionsPerBlock]
} }
maxFree := bc.config.MaxFreeTransactionsPerBlock maxFree := bc.config.MaxFreeTransactionsPerBlock
if maxFree != 0 { if maxFree != 0 {
lowStart := sort.Search(len(txes), func(i int) bool { 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) { if lowStart+maxFree < len(txes) {
txes = txes[:lowStart+maxFree] txes = txes[:lowStart+maxFree]
@ -1360,28 +1124,11 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
if netFee < 0 { if netFee < 0 {
return errors.Errorf("insufficient funds: net fee is %v, need %v", t.NetworkFee, needNetworkFee) 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 block == nil {
if ok := bc.memPool.Verify(t, bc); !ok { if ok := bc.memPool.Verify(t, bc); !ok {
return errors.New("invalid transaction due to conflicts with the memory pool") 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 { for _, a := range t.Attributes {
if a.Usage == transaction.ECDH02 || a.Usage == transaction.ECDH03 { 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) 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 // isTxStillRelevant is a callback for mempool transaction filtering after the
// new block addition. It returns false for transactions already present in the // new block addition. It returns false for transactions already present in the
// chain (added by the new block), transactions using some inputs that are // chain (added by the new block), transactions using some inputs that are
@ -1492,15 +1152,6 @@ func (bc *Blockchain) isTxStillRelevant(t *transaction.Transaction) bool {
if bc.dao.HasTransaction(t.Hash()) { if bc.dao.HasTransaction(t.Hash()) {
return false 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 { for i := range t.Scripts {
if !vm.IsStandardContract(t.Scripts[i].VerificationScript) { if !vm.IsStandardContract(t.Scripts[i].VerificationScript) {
recheckWitness = true recheckWitness = true
@ -1536,7 +1187,6 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
return err return err
} }
// Policying. // Policying.
if t.Type != transaction.ClaimType {
txSize := io.GetVarSize(t) txSize := io.GetVarSize(t)
maxFree := bc.config.MaxFreeTransactionSize maxFree := bc.config.MaxFreeTransactionSize
if maxFree != 0 && txSize > maxFree { if maxFree != 0 && txSize > maxFree {
@ -1545,7 +1195,6 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
return ErrPolicy return ErrPolicy
} }
} }
}
if err := bc.memPool.Add(t, bc); err != nil { if err := bc.memPool.Add(t, bc); err != nil {
switch err { switch err {
case mempool.ErrOOM: case mempool.ErrOOM:
@ -1559,125 +1208,6 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error {
return nil 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. //GetStandByValidators returns validators from the configuration.
func (bc *Blockchain) GetStandByValidators() (keys.PublicKeys, error) { func (bc *Blockchain) GetStandByValidators() (keys.PublicKeys, error) {
return getValidators(bc.config) return getValidators(bc.config)
@ -1697,57 +1227,11 @@ func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) {
// to verify whether the transaction is bonafide or not. // 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) // 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) { 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) 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 hashes[t.Sender] = true
for _, c := range t.Cosigners { for _, c := range t.Cosigners {
hashes[c.Account] = true 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 // convert hashes to []util.Uint160
hashesResult := make([]util.Uint160, 0, len(hashes)) hashesResult := make([]util.Uint160, 0, len(hashes))
for h := range 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 // is used for easy interop access and can be omitted for transactions that are
// not yet added into any block. // 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). // 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 { func (bc *Blockchain) verifyTxWitnesses(t *transaction.Transaction, block *block.Block) error {
hashes, err := bc.GetScriptHashesForVerifying(t) hashes, err := bc.GetScriptHashesForVerifying(t)
if err != nil { if err != nil {

View file

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

View file

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

View file

@ -15,7 +15,6 @@ type Cached struct {
DAO DAO
accounts map[util.Uint160]*state.Account accounts map[util.Uint160]*state.Account
contracts map[util.Uint160]*state.Contract contracts map[util.Uint160]*state.Contract
unspents map[util.Uint256]*state.UnspentCoin
balances map[util.Uint160]*state.NEP5Balances balances map[util.Uint160]*state.NEP5Balances
transfers map[util.Uint160]map[uint32]*state.NEP5TransferLog transfers map[util.Uint160]map[uint32]*state.NEP5TransferLog
} }
@ -24,10 +23,9 @@ type Cached struct {
func NewCached(d DAO) *Cached { func NewCached(d DAO) *Cached {
accs := make(map[util.Uint160]*state.Account) accs := make(map[util.Uint160]*state.Account)
ctrs := make(map[util.Uint160]*state.Contract) ctrs := make(map[util.Uint160]*state.Contract)
unspents := make(map[util.Uint256]*state.UnspentCoin)
balances := make(map[util.Uint160]*state.NEP5Balances) balances := make(map[util.Uint160]*state.NEP5Balances)
transfers := make(map[util.Uint160]map[uint32]*state.NEP5TransferLog) 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 // 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) 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. // GetNEP5Balances retrieves NEP5Balances for the acc.
func (cd *Cached) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) { func (cd *Cached) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) {
if bs := cd.balances[acc]; bs != nil { if bs := cd.balances[acc]; bs != nil {
@ -167,13 +151,6 @@ func (cd *Cached) Persist() (int, error) {
} }
buf.Reset() 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 { for acc, bs := range cd.balances {
err := cd.DAO.putNEP5Balances(acc, bs, buf) err := cd.DAO.putNEP5Balances(acc, bs, buf)
if err != nil { if err != nil {
@ -197,7 +174,6 @@ func (cd *Cached) GetWrapped() DAO {
return &Cached{cd.DAO.GetWrapped(), return &Cached{cd.DAO.GetWrapped(),
cd.accounts, cd.accounts,
cd.contracts, cd.contracts,
cd.unspents,
cd.balances, cd.balances,
cd.transfers, cd.transfers,
} }

View file

@ -23,9 +23,8 @@ type DAO interface {
GetAccountStateOrNew(hash util.Uint160) (*state.Account, error) GetAccountStateOrNew(hash util.Uint160) (*state.Account, error)
GetAndDecode(entity io.Serializable, key []byte) error GetAndDecode(entity io.Serializable, key []byte) error
GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error)
GetAssetState(assetID util.Uint256) (*state.Asset, error)
GetBatch() *storage.MemBatch 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) GetContractState(hash util.Uint160) (*state.Contract, error)
GetCurrentBlockHeight() (uint32, error) GetCurrentBlockHeight() (uint32, error)
GetCurrentHeaderHeight() (i uint32, h util.Uint256, err 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) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
GetUnspentCoinState(hash util.Uint256) (*state.UnspentCoin, error)
GetVersion() (string, error) GetVersion() (string, error)
GetWrapped() DAO GetWrapped() DAO
HasTransaction(hash util.Uint256) bool HasTransaction(hash util.Uint256) bool
IsDoubleClaim(claim *transaction.ClaimTX) bool
IsDoubleSpend(tx *transaction.Transaction) bool
Persist() (int, error) Persist() (int, error)
PutAccountState(as *state.Account) error PutAccountState(as *state.Account) error
PutAppExecResult(aer *state.AppExecResult) error PutAppExecResult(aer *state.AppExecResult) error
PutAssetState(as *state.Asset) error
PutContractState(cs *state.Contract) error PutContractState(cs *state.Contract) error
PutCurrentHeader(hashAndIndex []byte) error PutCurrentHeader(hashAndIndex []byte) error
PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error
PutUnspentCoinState(hash util.Uint256, ucs *state.UnspentCoin) error
PutVersion(v string) error PutVersion(v string) error
StoreAsBlock(block *block.Block, sysFee uint32) error StoreAsBlock(block *block.Block) error
StoreAsCurrentBlock(block *block.Block) error StoreAsCurrentBlock(block *block.Block) error
StoreAsTransaction(tx *transaction.Transaction, index uint32) error StoreAsTransaction(tx *transaction.Transaction, index uint32) error
putAccountState(as *state.Account, buf *io.BufBinWriter) error putAccountState(as *state.Account, buf *io.BufBinWriter) error
putNEP5Balances(acc util.Uint160, bs *state.NEP5Balances, 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. // 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. // -- 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. // -- start contracts.
// GetContractState returns contract state as recorded in the given // 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. // -- 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. // -- start notification event.
// GetAppExecResult gets application execution result from the // GetAppExecResult gets application execution result from the
@ -402,18 +346,18 @@ func makeStorageItemKey(scripthash util.Uint160, key []byte) []byte {
// -- other. // -- other.
// GetBlock returns Block by the given hash if it exists in the store. // GetBlock returns Block by the given hash if it exists in the store.
func (dao *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()) key := storage.AppendPrefix(storage.DataBlock, hash.BytesLE())
b, err := dao.Store.Get(key) b, err := dao.Store.Get(key)
if err != nil { if err != nil {
return nil, 0, err return nil, err
} }
block, err := block.NewBlockFromTrimmedBytes(b[4:]) block, err := block.NewBlockFromTrimmedBytes(b)
if err != nil { 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 // 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. // 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 ( var (
key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE()) key = storage.AppendPrefix(storage.DataBlock, block.Hash().BytesLE())
buf = io.NewBufBinWriter() buf = io.NewBufBinWriter()
) )
buf.WriteU32LE(sysFee)
b, err := block.Trim() b, err := block.Trim()
if err != nil { if err != nil {
return err return err
@ -569,35 +512,6 @@ func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32)
return dao.Store.Put(key, buf.Bytes()) 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 // Persist flushes all the changes made into the (supposedly) persistent
// underlying store. // underlying store.
func (dao *Simple) Persist() (int, error) { 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/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "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/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/random"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -59,17 +58,6 @@ func TestPutAndGetAccountStateOrNew(t *testing.T) {
require.Equal(t, accountState.ScriptHash, gotAccount.ScriptHash) 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) { func TestPutAndGetContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore()) dao := NewSimple(storage.NewMemoryStore())
contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}} contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}}
@ -94,25 +82,6 @@ func TestDeleteContractState(t *testing.T) {
require.Nil(t, gotContractState) 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) { func TestPutGetAppExecResult(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore()) dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256() hash := random.Uint256()
@ -155,7 +124,7 @@ func TestDeleteStorageItem(t *testing.T) {
func TestGetBlock_NotExists(t *testing.T) { func TestGetBlock_NotExists(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore()) dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256() hash := random.Uint256()
block, _, err := dao.GetBlock(hash) block, err := dao.GetBlock(hash)
require.Error(t, err) require.Error(t, err)
require.Nil(t, block) require.Nil(t, block)
} }
@ -171,12 +140,11 @@ func TestPutGetBlock(t *testing.T) {
}, },
} }
hash := b.Hash() hash := b.Hash()
err := dao.StoreAsBlock(b, 42) err := dao.StoreAsBlock(b)
require.NoError(t, err) require.NoError(t, err)
gotBlock, sysfee, err := dao.GetBlock(hash) gotBlock, err := dao.GetBlock(hash)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, gotBlock) require.NotNil(t, gotBlock)
require.EqualValues(t, 42, sysfee)
} }
func TestGetVersion_NoVersion(t *testing.T) { func TestGetVersion_NoVersion(t *testing.T) {
@ -221,7 +189,7 @@ func TestGetCurrentHeaderHeight_Store(t *testing.T) {
func TestStoreAsTransaction(t *testing.T) { func TestStoreAsTransaction(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore()) dao := NewSimple(storage.NewMemoryStore())
tx := transaction.NewIssueTX() tx := transaction.New([]byte{byte(opcode.PUSH1)}, 1)
hash := tx.Hash() hash := tx.Hash()
err := dao.StoreAsTransaction(tx, 0) err := dao.StoreAsTransaction(tx, 0)
require.NoError(t, err) require.NoError(t, err)

View file

@ -41,10 +41,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
} }
const ( 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 neoContractCreate = 0x6ea56cf6 // Neo.Contract.Create
neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate
antSharesContractCreate = 0x2a28d29b // AntShares.Contract.Create antSharesContractCreate = 0x2a28d29b // AntShares.Contract.Create
@ -58,11 +54,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
estack := v.Estack() estack := v.Estack()
switch id { 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: case neoContractCreate, neoContractMigrate, antSharesContractCreate, antSharesContractMigrate:
return smartcontract.GetDeploymentPrice(smartcontract.PropertyState(estack.Peek(3).BigInt().Int64())) return smartcontract.GetDeploymentPrice(smartcontract.PropertyState(estack.Peek(3).BigInt().Int64()))
case systemStoragePut, systemStoragePutEx, neoStoragePut, antSharesStoragePut: case systemStoragePut, systemStoragePutEx, neoStoragePut, antSharesStoragePut:

View file

@ -23,21 +23,6 @@ func TestGetPrice(t *testing.T) {
v := SpawnVM(systemInterop) v := SpawnVM(systemInterop)
v.SetPriceGetter(getPrice) 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) { t.Run("Neo.Contract.Create (no props)", func(t *testing.T) {
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position) // 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), 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{ Base: block.Base{
Version: 0, Version: 0,
PrevHash: prev, PrevHash: prev,
Timestamp: uint64(time.Now().UTC().Unix()) + uint64(index), Timestamp: uint64(time.Now().UTC().Unix())*1000 + uint64(index),
Index: index, Index: index,
NextConsensus: witness.ScriptHash(), NextConsensus: witness.ScriptHash(),
Script: witness, Script: witness,
@ -138,7 +138,7 @@ func newDumbBlock() *block.Block {
Nonce: 1111, Nonce: 1111,
}, },
Transactions: []*transaction.Transaction{ Transactions: []*transaction.Transaction{
{Type: transaction.IssueType}, transaction.New([]byte{byte(opcode.PUSH1)}, 0),
}, },
} }
} }
@ -165,19 +165,21 @@ func TestCreateBasicChain(t *testing.T) {
return testNonce return testNonce
} }
var neoAmount = util.Fixed8FromInt64(99999000) const neoAmount = 99999000
var neoRemainder = util.Fixed8FromInt64(100000000) - neoAmount
bc := newTestChain(t) bc := newTestChain(t)
defer bc.Close() defer bc.Close()
gasHash := bc.contracts.GAS.Hash gasHash := bc.contracts.GAS.Hash
neoHash := bc.contracts.NEO.Hash
t.Logf("native GAS hash: %v", gasHash) t.Logf("native GAS hash: %v", gasHash)
t.Logf("native NEO hash: %v", neoHash)
priv0 := testchain.PrivateKeyByID(0) priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash() priv0ScriptHash := priv0.GetScriptHash()
// Move almost all NEO and some nep5 GAS to one simple account. require.Equal(t, util.Fixed8FromInt64(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
txMoveNeo := newNEP5Transfer(gasHash, neoOwner, priv0ScriptHash, 1000000000) // Move some NEO to one simple account.
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
txMoveNeo.ValidUntilBlock = validUntilBlock txMoveNeo.ValidUntilBlock = validUntilBlock
txMoveNeo.Nonce = getNextNonce() txMoveNeo.Nonce = getNextNonce()
txMoveNeo.Sender = neoOwner txMoveNeo.Sender = neoOwner
@ -187,93 +189,34 @@ func TestCreateBasicChain(t *testing.T) {
AllowedContracts: nil, AllowedContracts: nil,
AllowedGroups: 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)) 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)) require.NoError(t, bc.AddBlock(b))
t.Logf("txMoveNeo: %s", txMoveNeo.Hash().StringLE()) 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 // info for getblockheader rpc tests
t.Logf("header hash: %s", b.Hash().StringLE()) t.Logf("header hash: %s", b.Hash().StringLE())
buf := io.NewBufBinWriter() buf := io.NewBufBinWriter()
b.Header().EncodeBinary(buf.BinWriter) b.Header().EncodeBinary(buf.BinWriter)
t.Logf("header: %s", hex.EncodeToString(buf.Bytes())) 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()) acc0, err := wallet.NewAccountFromWIF(priv0.WIF())
require.NoError(t, err) 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. // Push some contract into the chain.
avm, err := ioutil.ReadFile(prefix + "test_contract.avm") avm, err := ioutil.ReadFile(prefix + "test_contract.avm")
require.NoError(t, err) require.NoError(t, err)
@ -297,21 +240,10 @@ func TestCreateBasicChain(t *testing.T) {
txScript := script.Bytes() txScript := script.Bytes()
invFee := util.Fixed8FromFloat(100) invFee := util.Fixed8FromFloat(100)
txDeploy := transaction.NewInvocationTX(txScript, invFee) txDeploy := transaction.New(txScript, invFee)
txDeploy.Nonce = getNextNonce() txDeploy.Nonce = getNextNonce()
txDeploy.ValidUntilBlock = validUntilBlock txDeploy.ValidUntilBlock = validUntilBlock
txDeploy.Sender = priv0ScriptHash 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, addNetworkFee(bc, txDeploy, acc0))
require.NoError(t, acc0.SignTx(txDeploy)) require.NoError(t, acc0.SignTx(txDeploy))
b = bc.newBlock(txDeploy) b = bc.newBlock(txDeploy)
@ -322,7 +254,7 @@ func TestCreateBasicChain(t *testing.T) {
script = io.NewBufBinWriter() script = io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "Put", "testkey", "testvalue") 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.Nonce = getNextNonce()
txInv.ValidUntilBlock = validUntilBlock txInv.ValidUntilBlock = validUntilBlock
txInv.Sender = priv0ScriptHash txInv.Sender = priv0ScriptHash
@ -333,26 +265,18 @@ func TestCreateBasicChain(t *testing.T) {
t.Logf("txInv: %s", txInv.Hash().StringLE()) t.Logf("txInv: %s", txInv.Hash().StringLE())
priv1 := testchain.PrivateKeyByID(1) priv1 := testchain.PrivateKeyByID(1)
txNeo0to1 := transaction.NewContractTX() txNeo0to1 := newNEP5Transfer(neoHash, priv0ScriptHash, priv1.GetScriptHash(), 1000)
txNeo0to1.Nonce = getNextNonce() txNeo0to1.Nonce = getNextNonce()
txNeo0to1.ValidUntilBlock = validUntilBlock txNeo0to1.ValidUntilBlock = validUntilBlock
txNeo0to1.Sender = priv0ScriptHash txNeo0to1.Sender = priv0ScriptHash
txNeo0to1.Data = new(transaction.ContractTX) txNeo0to1.Cosigners = []transaction.Cosigner{
txNeo0to1.AddInput(&transaction.Input{ {
PrevHash: txNeoRound.Hash(), Account: priv0ScriptHash,
PrevIndex: 0, Scopes: transaction.CalledByEntry,
}) AllowedContracts: nil,
txNeo0to1.AddOutput(&transaction.Output{ AllowedGroups: nil,
AssetID: GoverningTokenID(), },
Amount: util.Fixed8FromInt64(1000), }
ScriptHash: priv1.GetScriptHash(),
})
txNeo0to1.AddOutput(&transaction.Output{
AssetID: GoverningTokenID(),
Amount: neoAmount - util.Fixed8FromInt64(1000),
ScriptHash: priv0.GetScriptHash(),
})
require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0)) require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0))
require.NoError(t, acc0.SignTx(txNeo0to1)) require.NoError(t, acc0.SignTx(txNeo0to1))
b = bc.newBlock(txNeo0to1) b = bc.newBlock(txNeo0to1)
@ -361,7 +285,7 @@ func TestCreateBasicChain(t *testing.T) {
sh := hash.Hash160(avm) sh := hash.Hash160(avm)
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, sh, "init") emit.AppCallWithOperationAndArgs(w.BinWriter, sh, "init")
initTx := transaction.NewInvocationTX(w.Bytes(), 0) initTx := transaction.New(w.Bytes(), 0)
initTx.Nonce = getNextNonce() initTx.Nonce = getNextNonce()
initTx.ValidUntilBlock = validUntilBlock initTx.ValidUntilBlock = validUntilBlock
initTx.Sender = priv0ScriptHash initTx.Sender = priv0ScriptHash
@ -384,7 +308,7 @@ func TestCreateBasicChain(t *testing.T) {
b = bc.newBlock(initTx, transferTx) b = bc.newBlock(initTx, transferTx)
require.NoError(t, bc.AddBlock(b)) 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 = newNEP5Transfer(sh, priv0.GetScriptHash(), priv1.GetScriptHash(), 123)
transferTx.Nonce = getNextNonce() transferTx.Nonce = getNextNonce()
@ -403,7 +327,7 @@ func TestCreateBasicChain(t *testing.T) {
b = bc.newBlock(transferTx) b = bc.newBlock(transferTx)
require.NoError(t, bc.AddBlock(b)) require.NoError(t, bc.AddBlock(b))
t.Logf("sendRublesTx: %v", transferTx.Hash().StringBE()) t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE())
if saveChain { if saveChain {
outStream, err := os.Create(prefix + "testblocks.acc") 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. // Prepare some transaction for future submission.
txNeoRound = transaction.NewContractTX() txSendRaw := newNEP5Transfer(neoHash, priv0ScriptHash, priv1.GetScriptHash(), int64(util.Fixed8FromInt64(1000)))
txNeoRound.Nonce = getNextNonce() txSendRaw.ValidUntilBlock = validUntilBlock
txNeoRound.ValidUntilBlock = validUntilBlock txSendRaw.Nonce = getNextNonce()
txNeoRound.Sender = priv0ScriptHash txSendRaw.Sender = priv0ScriptHash
txNeoRound.AddInput(&transaction.Input{ txSendRaw.Cosigners = []transaction.Cosigner{{
PrevHash: txNeo0to1.Hash(), Account: priv0ScriptHash,
PrevIndex: 1, Scopes: transaction.CalledByEntry,
}) AllowedContracts: nil,
txNeoRound.AddOutput(&transaction.Output{ AllowedGroups: nil,
AssetID: GoverningTokenID(), }}
Amount: neoAmount - util.Fixed8FromInt64(1000), require.NoError(t, addNetworkFee(bc, txSendRaw, acc0))
ScriptHash: priv0.GetScriptHash(), require.NoError(t, acc0.SignTx(txSendRaw))
Position: 0,
})
txNeoRound.Data = new(transaction.ContractTX)
require.NoError(t, addNetworkFee(bc, txNeoRound, acc0))
require.NoError(t, acc0.SignTx(txNeoRound))
bw := io.NewBufBinWriter() bw := io.NewBufBinWriter()
txNeoRound.EncodeBinary(bw.BinWriter) txSendRaw.EncodeBinary(bw.BinWriter)
t.Logf("sendrawtransaction: %s", hex.EncodeToString(bw.Bytes())) 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) emit.Opcode(w.BinWriter, opcode.ASSERT)
script := w.Bytes() script := w.Bytes()
return transaction.NewInvocationTX(script, 0) return transaction.New(script, 0)
} }
func addSender(txs ...*transaction.Transaction) error { func addSender(txs ...*transaction.Transaction) error {

View file

@ -4,20 +4,15 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"math"
"sort" "sort"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "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/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/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"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
gherr "github.com/pkg/errors"
) )
const ( const (
@ -29,15 +24,6 @@ const (
MaxContractParametersNum = 252 MaxContractParametersNum = 252
// MaxContractStringLen is the maximum length for contract metadata strings. // MaxContractStringLen is the maximum length for contract metadata strings.
MaxContractStringLen = 252 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. // headerGetVersion returns version from the header.
@ -88,106 +74,6 @@ func txGetAttributes(ic *interop.Context, v *vm.VM) error {
return nil 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. // txGetWitnesses returns current transaction witnesses.
func txGetWitnesses(ic *interop.Context, v *vm.VM) error { func txGetWitnesses(ic *interop.Context, v *vm.VM) error {
txInterface := v.Estack().Pop().Value() txInterface := v.Estack().Pop().Value()
@ -206,24 +92,6 @@ func txGetWitnesses(ic *interop.Context, v *vm.VM) error {
return nil 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. // witnessGetVerificationScript returns current witness' script.
func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error { func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error {
witInterface := v.Estack().Pop().Value() witInterface := v.Estack().Pop().Value()
@ -238,84 +106,6 @@ func witnessGetVerificationScript(ic *interop.Context, v *vm.VM) error {
return nil 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. // attrGetData returns tx attribute data.
func attrGetData(ic *interop.Context, v *vm.VM) error { func attrGetData(ic *interop.Context, v *vm.VM) error {
attrInterface := v.Estack().Pop().Value() attrInterface := v.Estack().Pop().Value()
@ -353,21 +143,6 @@ func bcGetAccount(ic *interop.Context, v *vm.VM) error {
return nil 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. // accountGetBalance returns balance for a given account.
func accountGetBalance(ic *interop.Context, v *vm.VM) error { func accountGetBalance(ic *interop.Context, v *vm.VM) error {
accInterface := v.Estack().Pop().Value() accInterface := v.Estack().Pop().Value()
@ -571,203 +346,6 @@ func contractMigrate(ic *interop.Context, v *vm.VM) error {
return contractDestroy(ic, v) 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. // runtimeSerialize serializes top stack item into a ByteArray.
func runtimeSerialize(_ *interop.Context, v *vm.VM) error { func runtimeSerialize(_ *interop.Context, v *vm.VM) error {
return vm.RuntimeSerialize(v) return vm.RuntimeSerialize(v)

View file

@ -25,18 +25,13 @@ import (
) )
/* Missing tests: /* Missing tests:
* TestTxGetReferences
* TestTxGetUnspentCoins
* TestTxGetWitnesses * TestTxGetWitnesses
* TestBcGetAccount * TestBcGetAccount
* TestBcGetAsset
* TestAccountGetBalance * TestAccountGetBalance
* TestAccountIsStandard * TestAccountIsStandard
* TestCreateContractStateFromVM * TestCreateContractStateFromVM
* TestContractCreate * TestContractCreate
* TestContractMigrate * TestContractMigrate
* TestAssetCreate
* TestAssetRenew
* TestRuntimeSerialize * TestRuntimeSerialize
* TestRuntimeDeserialize * TestRuntimeDeserialize
*/ */
@ -192,47 +187,6 @@ func TestTxGetAttributes(t *testing.T) {
require.Equal(t, tx.Attributes[0].Usage, value[0].Value().(*transaction.Attribute).Usage) 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) { func TestWitnessGetVerificationScript(t *testing.T) {
v := vm.New() v := vm.New()
script := []byte{byte(opcode.PUSHM1), byte(opcode.RET)} 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) { 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() msg := tx.GetSignedPart()
sign := priv.Sign(msg) sign := priv.Sign(msg)
runCase(t, false, true, sign, priv.PublicKey().Bytes(), vm.NewInteropItem(tx)) runCase(t, false, true, sign, priv.PublicKey().Bytes(), vm.NewInteropItem(tx))
}) })
t.Run("signed script container", func(t *testing.T) { 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() msg := tx.GetSignedPart()
sign := priv.Sign(msg) sign := priv.Sign(msg)
ic.Container = tx 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) { func TestAttrGetData(t *testing.T) {
v, tx, context, chain := createVMAndTX(t) v, tx, context, chain := createVMAndTX(t)
defer chain.Close() defer chain.Close()
@ -455,95 +334,7 @@ func TestContractIsPayable(t *testing.T) {
require.Equal(t, contractState.IsPayable(), isPayable) require.Equal(t, contractState.IsPayable(), isPayable)
} }
func TestAssetGetAdmin(t *testing.T) { // Helper functions to create VM, InteropContext, TX, Account, Contract.
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.
func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) { func createVMAndPushBlock(t *testing.T) (*vm.VM, *block.Block, *interop.Context, *Blockchain) {
v := vm.New() v := vm.New()
@ -560,29 +351,6 @@ func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop
return v, tx, context, chain 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) { func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) {
v := vm.New() v := vm.New()
contractState := &state.Contract{ 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) { func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Context, *Blockchain) {
v := vm.New() v := vm.New()
script := []byte{byte(opcode.PUSH1), byte(opcode.RET)} script := []byte{byte(opcode.PUSH1), byte(opcode.RET)}
tx := transaction.NewInvocationTX(script, 0) tx := transaction.New(script, 0)
bytes := make([]byte, 1) bytes := make([]byte, 1)
attributes := append(tx.Attributes, transaction.Attribute{ attributes := append(tx.Attributes, transaction.Attribute{
@ -625,21 +393,7 @@ func createVMAndTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop.Con
Data: bytes, 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.Attributes = attributes
tx.Inputs = inputs
tx.Outputs = outputs
chain := newTestChain(t) chain := newTestChain(t)
context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, tx) context := chain.newInteropContext(trigger.Application, dao.NewSimple(storage.NewMemoryStore()), nil, tx)
return v, tx, context, chain 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.GetBalance", Func: accountGetBalance, Price: 1},
{Name: "Neo.Account.GetScriptHash", Func: accountGetScriptHash, Price: 1}, {Name: "Neo.Account.GetScriptHash", Func: accountGetScriptHash, Price: 1},
{Name: "Neo.Account.IsStandard", Func: accountIsStandard, Price: 100}, {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.GetData", Func: attrGetData, Price: 1},
{Name: "Neo.Attribute.GetUsage", Func: attrGetUsage, Price: 1}, {Name: "Neo.Attribute.GetUsage", Func: attrGetUsage, Price: 1},
{Name: "Neo.Block.GetTransaction", Func: blockGetTransaction, Price: 1}, {Name: "Neo.Block.GetTransaction", Func: blockGetTransaction, Price: 1},
{Name: "Neo.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1}, {Name: "Neo.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1},
{Name: "Neo.Block.GetTransactions", Func: blockGetTransactions, Price: 1}, {Name: "Neo.Block.GetTransactions", Func: blockGetTransactions, Price: 1},
{Name: "Neo.Blockchain.GetAccount", Func: bcGetAccount, Price: 100}, {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.GetBlock", Func: bcGetBlock, Price: 200},
{Name: "Neo.Blockchain.GetContract", Func: bcGetContract, Price: 100}, {Name: "Neo.Blockchain.GetContract", Func: bcGetContract, Price: 100},
{Name: "Neo.Blockchain.GetHeader", Func: bcGetHeader, 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.GetPrevHash", Func: headerGetPrevHash, Price: 1},
{Name: "Neo.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1}, {Name: "Neo.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1},
{Name: "Neo.Header.GetVersion", Func: headerGetVersion, 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.Concat", Func: iterator.Concat, Price: 1},
{Name: "Neo.Iterator.Create", Func: iterator.Create, Price: 1}, {Name: "Neo.Iterator.Create", Func: iterator.Create, Price: 1},
{Name: "Neo.Iterator.Key", Func: iterator.Key, Price: 1}, {Name: "Neo.Iterator.Key", Func: iterator.Key, Price: 1},
{Name: "Neo.Iterator.Keys", Func: iterator.Keys, Price: 1}, {Name: "Neo.Iterator.Keys", Func: iterator.Keys, Price: 1},
{Name: "Neo.Iterator.Values", Func: iterator.Values, Price: 1}, {Name: "Neo.Iterator.Values", Func: iterator.Values, Price: 1},
{Name: "Neo.Native.Deploy", Func: native.Deploy, 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.CheckWitness", Func: runtime.CheckWitness, Price: 200},
{Name: "Neo.Runtime.Deserialize", Func: runtimeDeserialize, Price: 1}, {Name: "Neo.Runtime.Deserialize", Func: runtimeDeserialize, Price: 1},
{Name: "Neo.Runtime.GetTime", Func: runtimeGetTime, 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.StorageContext.AsReadOnly", Func: storageContextAsReadOnly, Price: 1},
{Name: "Neo.Transaction.GetAttributes", Func: txGetAttributes, Price: 1}, {Name: "Neo.Transaction.GetAttributes", Func: txGetAttributes, Price: 1},
{Name: "Neo.Transaction.GetHash", Func: txGetHash, 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.Transaction.GetWitnesses", Func: txGetWitnesses, Price: 200},
{Name: "Neo.Witness.GetVerificationScript", Func: witnessGetVerificationScript, Price: 100}, {Name: "Neo.Witness.GetVerificationScript", Func: witnessGetVerificationScript, Price: 100},
@ -191,23 +169,12 @@ var neoInterops = []interop.Function{
// Old compatibility APIs. // Old compatibility APIs.
{Name: "AntShares.Account.GetBalance", Func: accountGetBalance, Price: 1}, {Name: "AntShares.Account.GetBalance", Func: accountGetBalance, Price: 1},
{Name: "AntShares.Account.GetScriptHash", Func: accountGetScriptHash, 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.GetData", Func: attrGetData, Price: 1},
{Name: "AntShares.Attribute.GetUsage", Func: attrGetUsage, Price: 1}, {Name: "AntShares.Attribute.GetUsage", Func: attrGetUsage, Price: 1},
{Name: "AntShares.Block.GetTransaction", Func: blockGetTransaction, Price: 1}, {Name: "AntShares.Block.GetTransaction", Func: blockGetTransaction, Price: 1},
{Name: "AntShares.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1}, {Name: "AntShares.Block.GetTransactionCount", Func: blockGetTransactionCount, Price: 1},
{Name: "AntShares.Block.GetTransactions", Func: blockGetTransactions, Price: 1}, {Name: "AntShares.Block.GetTransactions", Func: blockGetTransactions, Price: 1},
{Name: "AntShares.Blockchain.GetAccount", Func: bcGetAccount, Price: 100}, {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.GetBlock", Func: bcGetBlock, Price: 200},
{Name: "AntShares.Blockchain.GetContract", Func: bcGetContract, Price: 100}, {Name: "AntShares.Blockchain.GetContract", Func: bcGetContract, Price: 100},
{Name: "AntShares.Blockchain.GetHeader", Func: bcGetHeader, 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.GetPrevHash", Func: headerGetPrevHash, Price: 1},
{Name: "AntShares.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1}, {Name: "AntShares.Header.GetTimestamp", Func: headerGetTimestamp, Price: 1},
{Name: "AntShares.Header.GetVersion", Func: headerGetVersion, 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.CheckWitness", Func: runtime.CheckWitness, Price: 200},
{Name: "AntShares.Runtime.Log", Func: runtimeLog, Price: 1}, {Name: "AntShares.Runtime.Log", Func: runtimeLog, Price: 1},
{Name: "AntShares.Runtime.Notify", Func: runtimeNotify, 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.Storage.Put", Func: storagePut, Price: 0},
{Name: "AntShares.Transaction.GetAttributes", Func: txGetAttributes, Price: 1}, {Name: "AntShares.Transaction.GetAttributes", Func: txGetAttributes, Price: 1},
{Name: "AntShares.Transaction.GetHash", Func: txGetHash, 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 // 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{ funcs := []func(*interop.Context, *vm.VM) error{
accountGetBalance, accountGetBalance,
accountGetScriptHash, accountGetScriptHash,
assetGetAdmin,
assetGetAmount,
assetGetAssetID,
assetGetAssetType,
assetGetAvailable,
assetGetIssuer,
assetGetOwner,
assetGetPrecision,
assetRenew,
attrGetData, attrGetData,
attrGetUsage, attrGetUsage,
blockGetTransaction, blockGetTransaction,
@ -58,12 +49,6 @@ func TestUnexpectedNonInterops(t *testing.T) {
headerGetPrevHash, headerGetPrevHash,
headerGetTimestamp, headerGetTimestamp,
headerGetVersion, headerGetVersion,
inputGetHash,
inputGetIndex,
invocationTxGetScript,
outputGetAssetID,
outputGetScriptHash,
outputGetValue,
storageContextAsReadOnly, storageContextAsReadOnly,
storageDelete, storageDelete,
storageFind, storageFind,
@ -72,11 +57,6 @@ func TestUnexpectedNonInterops(t *testing.T) {
storagePutEx, storagePutEx,
txGetAttributes, txGetAttributes,
txGetHash, txGetHash,
txGetInputs,
txGetOutputs,
txGetReferences,
txGetType,
txGetUnspentCoins,
txGetWitnesses, txGetWitnesses,
witnessGetVerificationScript, witnessGetVerificationScript,
} }

View file

@ -12,8 +12,8 @@ import (
var ( var (
// ErrConflict is returned when transaction being added is incompatible // ErrConflict is returned when transaction being added is incompatible
// with the contents of the memory pool (using the same inputs as some // with the contents of the memory pool (Sender doesn't have enough GAS
// other transaction in the pool) // to pay for all transactions in the pool).
ErrConflict = errors.New("conflicts with the memory pool") ErrConflict = errors.New("conflicts with the memory pool")
// ErrDup is returned when transaction being added is already present // ErrDup is returned when transaction being added is already present
// in the memory pool. // in the memory pool.
@ -33,12 +33,6 @@ type item struct {
// items is a slice of item. // items is a slice of item.
type items []*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 // utilityBalanceAndFees stores sender's balance and overall fees of
// sender's transactions which are currently in mempool // sender's transactions which are currently in mempool
type utilityBalanceAndFees struct { type utilityBalanceAndFees struct {
@ -51,8 +45,6 @@ type Pool struct {
lock sync.RWMutex lock sync.RWMutex
verifiedMap map[util.Uint256]*item verifiedMap map[util.Uint256]*item
verifiedTxes items verifiedTxes items
inputs []*transaction.Input
claims []*transaction.Input
fees map[util.Uint160]utilityBalanceAndFees fees map[util.Uint160]utilityBalanceAndFees
capacity int capacity int
@ -79,20 +71,6 @@ func (p *item) CompareTo(otherP *item) int {
return -1 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. // Fees sorted ascending.
if ret := p.txn.FeePerByte().CompareTo(otherP.txn.FeePerByte()); ret != 0 { if ret := p.txn.FeePerByte().CompareTo(otherP.txn.FeePerByte()); ret != 0 {
return ret return ret
@ -135,35 +113,6 @@ func (mp *Pool) containsKey(hash util.Uint256) bool {
return false 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 // 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 // and returns false if sender has not enough GAS to pay
func (mp *Pool) tryAddSendersFee(tx *transaction.Transaction, feer Feer) bool { 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) 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)) updateMempoolMetrics(len(mp.verifiedTxes))
mp.lock.Unlock() mp.lock.Unlock()
return nil return nil
@ -281,15 +218,6 @@ func (mp *Pool) Remove(hash util.Uint256) {
senderFee := mp.fees[it.txn.Sender] senderFee := mp.fees[it.txn.Sender]
senderFee.feeSum -= it.txn.SystemFee + it.txn.NetworkFee senderFee.feeSum -= it.txn.SystemFee + it.txn.NetworkFee
mp.fees[it.txn.Sender] = senderFee 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)) updateMempoolMetrics(len(mp.verifiedTxes))
mp.lock.Unlock() mp.lock.Unlock()
@ -303,34 +231,15 @@ func (mp *Pool) RemoveStale(isOK func(*transaction.Transaction) bool, feer Feer)
// We can reuse already allocated slice // We can reuse already allocated slice
// because items are iterated one-by-one in increasing order. // because items are iterated one-by-one in increasing order.
newVerifiedTxes := mp.verifiedTxes[:0] 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 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 { for _, itm := range mp.verifiedTxes {
if isOK(itm.txn) && mp.tryAddSendersFee(itm.txn, feer) { if isOK(itm.txn) && mp.tryAddSendersFee(itm.txn, feer) {
newVerifiedTxes = append(newVerifiedTxes, itm) 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 { } else {
delete(mp.verifiedMap, itm.txn.Hash()) 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.verifiedTxes = newVerifiedTxes
mp.inputs = newInputs
mp.claims = newClaims
mp.lock.Unlock() 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. // 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() mp.lock.RLock()
defer mp.lock.RUnlock() defer mp.lock.RUnlock()
if pItem, ok := mp.verifiedMap[hash]; ok { 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 // GetVerifiedTransactions returns a slice of transactions with their fees.
// whose hash is not included in excludedHashes. func (mp *Pool) GetVerifiedTransactions() []*transaction.Transaction {
func (mp *Pool) GetVerifiedTransactions() []TxWithFee {
mp.lock.RLock() mp.lock.RLock()
defer mp.lock.RUnlock() defer mp.lock.RUnlock()
var t = make([]TxWithFee, len(mp.verifiedTxes)) var t = make([]*transaction.Transaction, len(mp.verifiedTxes))
for i := range mp.verifiedTxes { for i := range mp.verifiedTxes {
t[i].Tx = mp.verifiedTxes[i].txn t[i] = mp.verifiedTxes[i].txn
t[i].Fee = mp.verifiedTxes[i].txn.NetworkFee
} }
return t 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. // checkTxConflicts is an internal unprotected version of Verify.
func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) bool { func (mp *Pool) checkTxConflicts(tx *transaction.Transaction, fee Feer) bool {
if areInputsInPool(tx.Inputs, mp.inputs) { return mp.checkBalanceAndUpdate(tx, fee)
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
} }
// Verify verifies if the inputs of a transaction tx are already used in any other transaction in the memory pool. // Verify checks if a Sender of tx is able to pay for it (and all the other
// If yes, the transaction tx is not a valid transaction and the function return false. // transactions in the pool). If yes, the transaction tx is a valid
// If no, the transaction tx is a valid transaction and the function return true. // 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 { func (mp *Pool) Verify(tx *transaction.Transaction, feer Feer) bool {
mp.lock.RLock() mp.lock.RLock()
defer mp.lock.RUnlock() defer mp.lock.RUnlock()

View file

@ -5,8 +5,8 @@ import (
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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) { func testMemPoolAddRemoveWithFeer(t *testing.T, fs Feer) {
mp := NewMemPool(10) mp := NewMemPool(10)
tx := transaction.NewContractTX() tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = 0 tx.Nonce = 0
_, _, ok := mp.TryGetValue(tx.Hash()) _, ok := mp.TryGetValue(tx.Hash())
require.Equal(t, false, ok) require.Equal(t, false, ok)
require.NoError(t, mp.Add(tx, fs)) require.NoError(t, mp.Add(tx, fs))
// Re-adding should fail. // Re-adding should fail.
require.Error(t, mp.Add(tx, fs)) 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, true, ok)
require.Equal(t, tx, tx2) require.Equal(t, tx, tx2)
mp.Remove(tx.Hash()) mp.Remove(tx.Hash())
_, _, ok = mp.TryGetValue(tx.Hash()) _, ok = mp.TryGetValue(tx.Hash())
require.Equal(t, false, ok) require.Equal(t, false, ok)
// Make sure nothing left in the mempool after removal. // Make sure nothing left in the mempool after removal.
assert.Equal(t, 0, len(mp.verifiedMap)) 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) }) 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) { func TestOverCapacity(t *testing.T) {
var fs = &FeerStub{lowPriority: true} var fs = &FeerStub{lowPriority: true}
const mempoolSize = 10 const mempoolSize = 10
mp := NewMemPool(mempoolSize) mp := NewMemPool(mempoolSize)
for i := 0; i < mempoolSize; i++ { for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX() tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = uint32(i) tx.Nonce = uint32(i)
require.NoError(t, mp.Add(tx, fs)) 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, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) 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. // Fees are also prioritized.
for i := 0; i < mempoolSize-1; i++ { for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX() tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Attributes = append(tx.Attributes, transaction.Attribute{ tx.Attributes = append(tx.Attributes, transaction.Attribute{
Usage: transaction.Hash1, Usage: transaction.Hash1,
Data: util.Uint256{1, 2, 3, 4}.BytesBE(), 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))) require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes)))
} }
// Less prioritized txes are not allowed anymore. // 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{ tx.Attributes = append(tx.Attributes, transaction.Attribute{
Usage: transaction.Hash1, Usage: transaction.Hash1,
Data: util.Uint256{1, 2, 3, 4}.BytesBE(), Data: util.Uint256{1, 2, 3, 4}.BytesBE(),
}) })
tx.NetworkFee = util.Fixed8FromFloat(0.00001) tx.NetworkFee = util.Fixed8FromFloat(0.000001)
tx.Nonce = txcnt tx.Nonce = txcnt
txcnt++ txcnt++
require.Error(t, mp.Add(tx, fs)) require.Error(t, mp.Add(tx, fs))
require.Equal(t, mempoolSize, mp.Count()) require.Equal(t, mempoolSize, mp.Count())
require.Equal(t, true, sort.IsSorted(sort.Reverse(mp.verifiedTxes))) 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. // 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.Nonce = txcnt
tx.NetworkFee = util.Fixed8FromFloat(0.00007) tx.NetworkFee = util.Fixed8FromFloat(0.00007)
txcnt++ txcnt++
@ -273,7 +111,7 @@ func TestOverCapacity(t *testing.T) {
// High priority always wins over low priority. // High priority always wins over low priority.
fs.lowPriority = false fs.lowPriority = false
for i := 0; i < mempoolSize; i++ { for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX() tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = txcnt tx.Nonce = txcnt
txcnt++ txcnt++
require.NoError(t, mp.Add(tx, fs)) require.NoError(t, mp.Add(tx, fs))
@ -282,7 +120,7 @@ func TestOverCapacity(t *testing.T) {
} }
// Good luck with low priority now. // Good luck with low priority now.
fs.lowPriority = true fs.lowPriority = true
tx = transaction.NewContractTX() tx = transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = txcnt tx.Nonce = txcnt
require.Error(t, mp.Add(tx, fs)) require.Error(t, mp.Add(tx, fs))
require.Equal(t, mempoolSize, mp.Count()) require.Equal(t, mempoolSize, mp.Count())
@ -296,7 +134,7 @@ func TestGetVerified(t *testing.T) {
txes := make([]*transaction.Transaction, 0, mempoolSize) txes := make([]*transaction.Transaction, 0, mempoolSize)
for i := 0; i < mempoolSize; i++ { for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX() tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = uint32(i) tx.Nonce = uint32(i)
txes = append(txes, tx) txes = append(txes, tx)
require.NoError(t, mp.Add(tx, fs)) require.NoError(t, mp.Add(tx, fs))
@ -304,9 +142,7 @@ func TestGetVerified(t *testing.T) {
require.Equal(t, mempoolSize, mp.Count()) require.Equal(t, mempoolSize, mp.Count())
verTxes := mp.GetVerifiedTransactions() verTxes := mp.GetVerifiedTransactions()
require.Equal(t, mempoolSize, len(verTxes)) require.Equal(t, mempoolSize, len(verTxes))
for _, txf := range verTxes { require.ElementsMatch(t, txes, verTxes)
require.Contains(t, txes, txf.Tx)
}
for _, tx := range txes { for _, tx := range txes {
mp.Remove(tx.Hash()) mp.Remove(tx.Hash())
} }
@ -322,7 +158,7 @@ func TestRemoveStale(t *testing.T) {
txes1 := make([]*transaction.Transaction, 0, mempoolSize/2) txes1 := make([]*transaction.Transaction, 0, mempoolSize/2)
txes2 := make([]*transaction.Transaction, 0, mempoolSize/2) txes2 := make([]*transaction.Transaction, 0, mempoolSize/2)
for i := 0; i < mempoolSize; i++ { for i := 0; i < mempoolSize; i++ {
tx := transaction.NewContractTX() tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx.Nonce = uint32(i) tx.Nonce = uint32(i)
if i%2 == 0 { if i%2 == 0 {
txes1 = append(txes1, tx) txes1 = append(txes1, tx)
@ -343,15 +179,15 @@ func TestRemoveStale(t *testing.T) {
require.Equal(t, mempoolSize/2, mp.Count()) require.Equal(t, mempoolSize/2, mp.Count())
verTxes := mp.GetVerifiedTransactions() verTxes := mp.GetVerifiedTransactions()
for _, txf := range verTxes { for _, txf := range verTxes {
require.NotContains(t, txes1, txf.Tx) require.NotContains(t, txes1, txf)
require.Contains(t, txes2, txf.Tx) require.Contains(t, txes2, txf)
} }
} }
func TestMemPoolFees(t *testing.T) { func TestMemPoolFees(t *testing.T) {
mp := NewMemPool(10) mp := NewMemPool(10)
sender0 := util.Uint160{1, 2, 3} sender0 := util.Uint160{1, 2, 3}
tx0 := transaction.NewContractTX() tx0 := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
tx0.NetworkFee = util.Fixed8FromInt64(11000) tx0.NetworkFee = util.Fixed8FromInt64(11000)
tx0.Sender = sender0 tx0.Sender = sender0
// insufficient funds to add transaction, but balance should be stored // insufficient funds to add transaction, but balance should be stored
@ -364,7 +200,7 @@ func TestMemPoolFees(t *testing.T) {
}, mp.fees[sender0]) }, mp.fees[sender0])
// no problems with adding another transaction with lower fee // 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.NetworkFee = util.Fixed8FromInt64(7000)
tx1.Sender = sender0 tx1.Sender = sender0
require.NoError(t, mp.Add(tx1, &FeerStub{})) require.NoError(t, mp.Add(tx1, &FeerStub{}))
@ -375,7 +211,7 @@ func TestMemPoolFees(t *testing.T) {
}, mp.fees[sender0]) }, mp.fees[sender0])
// balance shouldn't change after adding one more transaction // 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.NetworkFee = util.Fixed8FromFloat(3000)
tx2.Sender = sender0 tx2.Sender = sender0
require.NoError(t, mp.Add(tx2, &FeerStub{})) require.NoError(t, mp.Add(tx2, &FeerStub{}))
@ -387,7 +223,7 @@ func TestMemPoolFees(t *testing.T) {
}, mp.fees[sender0]) }, mp.fees[sender0])
// can't add more transactions as we don't have enough GAS // 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.NetworkFee = util.Fixed8FromFloat(0.5)
tx3.Sender = sender0 tx3.Sender = sender0
require.Equal(t, false, mp.Verify(tx3, &FeerStub{})) 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/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
) )
// GAS represents GAS native contract. // GAS represents GAS native contract.
@ -40,11 +38,6 @@ func NewGAS() *GAS {
g.nep5TokenNative = *nep5 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 return g
} }
@ -98,16 +91,6 @@ func (g *GAS) OnPersist(ic *interop.Context) error {
return nil 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) { func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, []*keys.PublicKey, error) {
vs, err := ic.Chain.GetStandByValidators() vs, err := ic.Chain.GetStandByValidators()
if err != nil { 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 { if ic.Block == nil || ic.Block.Index == 0 {
return nil return nil
} }
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(acc.Balance.Int64()), acc.BalanceHeight, ic.Block.Index) gen := ic.Chain.CalculateClaimable(acc.Balance.Int64(), acc.BalanceHeight, ic.Block.Index)
if err != nil {
return err
}
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 return nil
} }
@ -206,11 +203,8 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []vm.StackItem) vm.StackIte
} }
tr := bs.Trackers[n.Hash] tr := bs.Trackers[n.Hash]
sys, net, err := ic.Chain.CalculateClaimable(util.Fixed8(tr.Balance), tr.LastUpdatedBlock, end) gen := ic.Chain.CalculateClaimable(tr.Balance, tr.LastUpdatedBlock, end)
if err != nil { return vm.NewBigIntegerItem(big.NewInt(int64(gen)))
panic(err)
}
return vm.NewBigIntegerItem(big.NewInt(int64(sys.Add(net))))
} }
func (n *NEO) registerValidator(ic *interop.Context, args []vm.StackItem) vm.StackItem { 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 inc := amount
if isEmpty { if isEmpty {
inc = big.NewInt(0) inc = big.NewInt(0)
} else {
inc = new(big.Int).Neg(inc)
} }
if err := c.incBalance(ic, from, siFrom, inc); err != nil { if err := c.incBalance(ic, from, siFrom, inc); err != nil {
return err 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) { func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) {
if amount.Sign() == 0 {
return
}
c.addTokens(ic, h, amount) c.addTokens(ic, h, amount)
c.emitTransfer(ic, nil, &h, amount) c.emitTransfer(ic, nil, &h, amount)
} }
func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) { func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) {
if amount.Sign() == 0 {
return
}
amount = new(big.Int).Neg(amount) amount = new(big.Int).Neg(amount)
c.addTokens(ic, h, amount) c.addTokens(ic, h, amount)
c.emitTransfer(ic, &h, nil, amount) c.emitTransfer(ic, &h, nil, amount)

View file

@ -92,7 +92,7 @@ func TestNativeContract_Invoke(t *testing.T) {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "sum", int64(14), int64(28)) emit.AppCallWithOperationAndArgs(w.BinWriter, tn.Metadata().Hash, "sum", int64(14), int64(28))
script := w.Bytes() script := w.Bytes()
tx := transaction.NewInvocationTX(script, 0) tx := transaction.New(script, 0)
validUntil := chain.blockHeight + 1 validUntil := chain.blockHeight + 1
tx.ValidUntilBlock = validUntil tx.ValidUntilBlock = validUntil
require.NoError(t, addSender(tx)) 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"` Value util.Fixed8 `json:"value"`
} }
// UnclaimedBalance represents transaction output which was spent and
// can be claimed.
type UnclaimedBalance struct {
Tx util.Uint256
Index uint16
Start uint32
End uint32
Value util.Fixed8
}
// UnspentBalances is a slice of UnspentBalance (mostly needed to sort them). // UnspentBalances is a slice of UnspentBalance (mostly needed to sort them).
type UnspentBalances []UnspentBalance type UnspentBalances []UnspentBalance
@ -32,7 +22,6 @@ type Account struct {
ScriptHash util.Uint160 ScriptHash util.Uint160
IsFrozen bool IsFrozen bool
Balances map[util.Uint256][]UnspentBalance Balances map[util.Uint256][]UnspentBalance
Unclaimed UnclaimedBalances
} }
// NewAccount returns a new Account object. // NewAccount returns a new Account object.
@ -42,7 +31,6 @@ func NewAccount(scriptHash util.Uint160) *Account {
ScriptHash: scriptHash, ScriptHash: scriptHash,
IsFrozen: false, IsFrozen: false,
Balances: make(map[util.Uint256][]UnspentBalance), 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 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. // EncodeBinary encodes Account to the given BinWriter.
@ -84,9 +68,6 @@ func (s *Account) EncodeBinary(bw *io.BinWriter) {
v[i].EncodeBinary(bw) v[i].EncodeBinary(bw)
} }
} }
bw.WriteVarUint(uint64(s.Unclaimed.Size()))
bw.WriteBytes(s.Unclaimed.Raw)
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
@ -103,24 +84,6 @@ func (u *UnspentBalance) EncodeBinary(w *io.BinWriter) {
u.Value.EncodeBinary(w) u.Value.EncodeBinary(w)
} }
// DecodeBinary implements io.Serializable interface.
func (u *UnclaimedBalance) DecodeBinary(r *io.BinReader) {
u.Tx.DecodeBinary(r)
u.Index = r.ReadU16LE()
u.Start = r.ReadU32LE()
u.End = r.ReadU32LE()
u.Value.DecodeBinary(r)
}
// EncodeBinary implements io.Serializable interface.
func (u *UnclaimedBalance) EncodeBinary(w *io.BinWriter) {
u.Tx.EncodeBinary(w)
w.WriteU16LE(u.Index)
w.WriteU32LE(u.Start)
w.WriteU32LE(u.End)
u.Value.EncodeBinary(w)
}
// GetBalanceValues sums all unspent outputs and returns a map of asset IDs to // GetBalanceValues sums all unspent outputs and returns a map of asset IDs to
// overall balances. // overall balances.
func (s *Account) GetBalanceValues() map[util.Uint256]util.Fixed8 { func (s *Account) GetBalanceValues() map[util.Uint256]util.Fixed8 {

View file

@ -30,7 +30,6 @@ func TestDecodeEncodeAccountState(t *testing.T) {
ScriptHash: random.Uint160(), ScriptHash: random.Uint160(),
IsFrozen: true, IsFrozen: true,
Balances: balances, Balances: balances,
Unclaimed: UnclaimedBalances{Raw: []byte{}},
} }
testserdes.EncodeDecodeBinary(t, a, new(Account)) 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 DataBlock KeyPrefix = 0x01
DataTransaction KeyPrefix = 0x02 DataTransaction KeyPrefix = 0x02
STAccount KeyPrefix = 0x40 STAccount KeyPrefix = 0x40
STCoin KeyPrefix = 0x44
STSpentCoin KeyPrefix = 0x45
STValidator KeyPrefix = 0x48
STAsset KeyPrefix = 0x4c
STNotification KeyPrefix = 0x4d STNotification KeyPrefix = 0x4d
STContract KeyPrefix = 0x50 STContract KeyPrefix = 0x50
STStorage KeyPrefix = 0x70 STStorage KeyPrefix = 0x70

View file

@ -11,9 +11,6 @@ var (
DataBlock, DataBlock,
DataTransaction, DataTransaction,
STAccount, STAccount,
STCoin,
STValidator,
STAsset,
STContract, STContract,
STStorage, STStorage,
IXHeaderHashList, IXHeaderHashList,
@ -26,9 +23,6 @@ var (
0x01, 0x01,
0x02, 0x02,
0x40, 0x40,
0x44,
0x48,
0x4c,
0x50, 0x50,
0x70, 0x70,
0x80, 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 ( var (
//TODO NEO3.0: Update binary //TODO NEO3.0: Update binary
// https://neotracker.io/tx/2c6a45547b3898318e400e541628990a07acb00f3b9a15a8e966ae49525304da
rawClaimTX = "020004bc67ba325d6412ff4c55b10f7e9afb54bbb2228d201b37363c3d697ac7c198f70300591cd454d7318d2087c0196abfbbd1573230380672f0f0cd004dcb4857e58cbd010031bcfbed573f5318437e95edd603922a4455ff3326a979fdd1c149a84c4cb0290000b51eb6159c58cac4fe23d90e292ad2bcb7002b0da2c474e81e1889c0649d2c490000000001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c603b555f00000000005d9de59d99c0d1f6ed1496444473f4a0b538302f014140456349cec43053009accdb7781b0799c6b591c812768804ab0a0b56b5eae7a97694227fcd33e70899c075848b2cee8fae733faac6865b484d3f7df8949e2aadb232103945fae1ed3c31d778f149192b76734fcc951b400ba3598faa81ff92ebe477eacac"
// https://neotracker.io/tx/fe4b3af60677204c57e573a57bdc97bc5059b05ad85b1474f84431f88d910f64 // https://neotracker.io/tx/fe4b3af60677204c57e573a57bdc97bc5059b05ad85b1474f84431f88d910f64
rawInvocationTX = "d101590400b33f7114839c33710da24cf8e7d536b8d244f3991cf565c8146063795d3b9b3cd55aef026eae992b91063db0db53c1087472616e7366657267c5cc1cb5392019e2cc4e6d6b5ea54c8d4b6d11acf166cb072961424c54f6000000000000000001206063795d3b9b3cd55aef026eae992b91063db0db0000014140c6a131c55ca38995402dff8e92ac55d89cbed4b98dfebbcb01acbc01bd78fa2ce2061be921b8999a9ab79c2958875bccfafe7ce1bbbaf1f56580815ea3a4feed232102d41ddce2c97be4c9aa571b8a32cbc305aa29afffbcae71b0ef568db0e93929aaac" 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/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "math/rand"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
@ -26,9 +26,6 @@ const (
// Transaction is a process recorded in the NEO blockchain. // Transaction is a process recorded in the NEO blockchain.
type Transaction struct { type Transaction struct {
// The type of the transaction.
Type TXType
// The trading version which is currently 0. // The trading version which is currently 0.
Version uint8 Version uint8
@ -48,9 +45,8 @@ type Transaction struct {
// transaction should fail verification. // transaction should fail verification.
ValidUntilBlock uint32 ValidUntilBlock uint32
// Data specific to the type of the transaction. // Code to run in NeoVM for this transaction.
// This is always a pointer to a <Type>Transaction. Script []byte
Data TXer
// Transaction attributes. // Transaction attributes.
Attributes []Attribute Attributes []Attribute
@ -58,12 +54,6 @@ type Transaction struct {
// Transaction cosigners (not include Sender). // Transaction cosigners (not include Sender).
Cosigners []Cosigner Cosigners []Cosigner
// The inputs of the transaction.
Inputs []Input
// The outputs of the transaction.
Outputs []Output
// The scripts that comes with this transaction. // The scripts that comes with this transaction.
// Scripts exist out of the verification script // Scripts exist out of the verification script
// and invocation 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. // Hash returns the hash of the transaction.
func (t *Transaction) Hash() util.Uint256 { func (t *Transaction) Hash() util.Uint256 {
if t.hash.Equals(util.Uint256{}) { if t.hash.Equals(util.Uint256{}) {
@ -109,20 +113,13 @@ func (t *Transaction) VerificationHash() util.Uint256 {
return t.verificationHash 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. // DecodeBinary implements Serializable interface.
func (t *Transaction) DecodeBinary(br *io.BinReader) { func (t *Transaction) DecodeBinary(br *io.BinReader) {
t.Type = TXType(br.ReadB())
t.Version = uint8(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.Nonce = br.ReadU32LE()
t.Sender.DecodeBinary(br) t.Sender.DecodeBinary(br)
t.SystemFee.DecodeBinary(br) t.SystemFee.DecodeBinary(br)
@ -140,7 +137,6 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
return return
} }
t.ValidUntilBlock = br.ReadU32LE() t.ValidUntilBlock = br.ReadU32LE()
t.decodeData(br)
br.ReadArray(&t.Attributes) br.ReadArray(&t.Attributes)
@ -154,14 +150,12 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
} }
} }
br.ReadArray(&t.Inputs) t.Script = br.ReadVarBytes()
br.ReadArray(&t.Outputs) if br.Err == nil && len(t.Script) == 0 {
for i := range t.Outputs { br.Err = errors.New("no script")
if t.Outputs[i].Amount.LessThan(0) {
br.Err = errors.New("negative output")
return return
} }
}
br.ReadArray(&t.Scripts) br.ReadArray(&t.Scripts)
// Create the hash of the transaction at decode, so we dont need // Create the hash of the transaction at decode, so we dont need
@ -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. // EncodeBinary implements Serializable interface.
func (t *Transaction) EncodeBinary(bw *io.BinWriter) { func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
t.encodeHashableFields(bw) t.encodeHashableFields(bw)
@ -202,12 +174,10 @@ func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
// encodeHashableFields encodes the fields that are not used for // encodeHashableFields encodes the fields that are not used for
// signing the transaction, which are all fields except the scripts. // signing the transaction, which are all fields except the scripts.
func (t *Transaction) encodeHashableFields(bw *io.BinWriter) { func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
noData := t.Type == ContractType if len(t.Script) == 0 {
if t.Data == nil && !noData { bw.Err = errors.New("transaction has no script")
bw.Err = errors.New("transaction has no data")
return return
} }
bw.WriteB(byte(t.Type))
bw.WriteB(byte(t.Version)) bw.WriteB(byte(t.Version))
bw.WriteU32LE(t.Nonce) bw.WriteU32LE(t.Nonce)
t.Sender.EncodeBinary(bw) t.Sender.EncodeBinary(bw)
@ -215,22 +185,13 @@ func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
t.NetworkFee.EncodeBinary(bw) t.NetworkFee.EncodeBinary(bw)
bw.WriteU32LE(t.ValidUntilBlock) bw.WriteU32LE(t.ValidUntilBlock)
// Underlying TXer.
if !noData {
t.Data.EncodeBinary(bw)
}
// Attributes // Attributes
bw.WriteArray(t.Attributes) bw.WriteArray(t.Attributes)
// Cosigners // Cosigners
bw.WriteArray(t.Cosigners) bw.WriteArray(t.Cosigners)
// Inputs bw.WriteVarBytes(t.Script)
bw.WriteArray(t.Inputs)
// Outputs
bw.WriteArray(t.Outputs)
} }
// createHash creates the hash of the transaction. // createHash creates the hash of the transaction.
@ -248,16 +209,6 @@ func (t *Transaction) createHash() error {
return nil 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. // GetSignedPart returns a part of the transaction which must be signed.
func (t *Transaction) GetSignedPart() []byte { func (t *Transaction) GetSignedPart() []byte {
buf := io.NewBufBinWriter() buf := io.NewBufBinWriter()
@ -300,7 +251,6 @@ func (t *Transaction) FeePerByte() util.Fixed8 {
type transactionJSON struct { type transactionJSON struct {
TxID util.Uint256 `json:"txid"` TxID util.Uint256 `json:"txid"`
Size int `json:"size"` Size int `json:"size"`
Type TXType `json:"type"`
Version uint8 `json:"version"` Version uint8 `json:"version"`
Nonce uint32 `json:"nonce"` Nonce uint32 `json:"nonce"`
Sender string `json:"sender"` Sender string `json:"sender"`
@ -309,14 +259,8 @@ type transactionJSON struct {
ValidUntilBlock uint32 `json:"valid_until_block"` ValidUntilBlock uint32 `json:"valid_until_block"`
Attributes []Attribute `json:"attributes"` Attributes []Attribute `json:"attributes"`
Cosigners []Cosigner `json:"cosigners"` Cosigners []Cosigner `json:"cosigners"`
Inputs []Input `json:"vin"` Script string `json:"script"`
Outputs []Output `json:"vout"`
Scripts []Witness `json:"scripts"` 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. // MarshalJSON implements json.Marshaler interface.
@ -324,36 +268,17 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
tx := transactionJSON{ tx := transactionJSON{
TxID: t.Hash(), TxID: t.Hash(),
Size: io.GetVarSize(t), Size: io.GetVarSize(t),
Type: t.Type,
Version: t.Version, Version: t.Version,
Nonce: t.Nonce, Nonce: t.Nonce,
Sender: address.Uint160ToString(t.Sender), Sender: address.Uint160ToString(t.Sender),
ValidUntilBlock: t.ValidUntilBlock, ValidUntilBlock: t.ValidUntilBlock,
Attributes: t.Attributes, Attributes: t.Attributes,
Cosigners: t.Cosigners, Cosigners: t.Cosigners,
Inputs: t.Inputs, Script: hex.EncodeToString(t.Script),
Outputs: t.Outputs,
Scripts: t.Scripts, Scripts: t.Scripts,
SystemFee: t.SystemFee, SystemFee: t.SystemFee,
NetworkFee: t.NetworkFee, 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) return json.Marshal(tx)
} }
@ -363,14 +288,11 @@ func (t *Transaction) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, tx); err != nil { if err := json.Unmarshal(data, tx); err != nil {
return err return err
} }
t.Type = tx.Type
t.Version = tx.Version t.Version = tx.Version
t.Nonce = tx.Nonce t.Nonce = tx.Nonce
t.ValidUntilBlock = tx.ValidUntilBlock t.ValidUntilBlock = tx.ValidUntilBlock
t.Attributes = tx.Attributes t.Attributes = tx.Attributes
t.Cosigners = tx.Cosigners t.Cosigners = tx.Cosigners
t.Inputs = tx.Inputs
t.Outputs = tx.Outputs
t.Scripts = tx.Scripts t.Scripts = tx.Scripts
t.SystemFee = tx.SystemFee t.SystemFee = tx.SystemFee
t.NetworkFee = tx.NetworkFee t.NetworkFee = tx.NetworkFee
@ -379,39 +301,10 @@ func (t *Transaction) UnmarshalJSON(data []byte) error {
return errors.New("cannot unmarshal tx: bad sender") return errors.New("cannot unmarshal tx: bad sender")
} }
t.Sender = sender t.Sender = sender
switch tx.Type { t.Script, err = hex.DecodeString(tx.Script)
case ClaimType:
t.Data = &ClaimTX{
Claims: tx.Claims,
}
case InvocationType:
bytes, err := hex.DecodeString(tx.Script)
if err != nil { if err != nil {
return err 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{}
}
if t.Hash() != tx.TxID { if t.Hash() != tx.TxID {
return errors.New("txid doesn't match transaction hash") return errors.New("txid doesn't match transaction hash")
} }

View file

@ -4,7 +4,6 @@ import (
"encoding/hex" "encoding/hex"
"testing" "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/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -36,31 +35,6 @@ func TestWitnessEncodeDecode(t *testing.T) {
// TODO NEO3.0: update binary // 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) { func TestDecodeEncodeInvocationTX(t *testing.T) {
tx := decodeTransaction(rawInvocationTX, t) tx := decodeTransaction(rawInvocationTX, t)
@ -87,117 +61,33 @@ func TestDecodeEncodeInvocationTX(t *testing.T) {
} }
*/ */
func TestNewInvocationTX(t *testing.T) { func TestNew(t *testing.T) {
script := []byte{0x51} script := []byte{0x51}
tx := NewInvocationTX(script, 1) tx := New(script, 1)
txData := tx.Data.(*InvocationTX) assert.Equal(t, util.Fixed8(1), tx.SystemFee)
assert.Equal(t, InvocationType, tx.Type) assert.Equal(t, script, tx.Script)
assert.Equal(t, tx.Version, txData.Version)
assert.Equal(t, script, txData.Script)
// Update hash fields to match tx2 that is gonna autoupdate them on decode. // Update hash fields to match tx2 that is gonna autoupdate them on decode.
_ = tx.Hash() _ = tx.Hash()
testserdes.EncodeDecodeBinary(t, tx, new(Transaction)) testserdes.EncodeDecodeBinary(t, tx, new(Transaction))
} }
func TestEncodingTXWithNoData(t *testing.T) { func TestEncodingTXWithNoScript(t *testing.T) {
_, err := testserdes.EncodeBinary(new(Transaction)) _, err := testserdes.EncodeBinary(new(Transaction))
require.Error(t, err) require.Error(t, err)
} }
func TestMarshalUnmarshalJSONContractTX(t *testing.T) { func TestDecodingTXWithNoScript(t *testing.T) {
tx := NewContractTX() txBin, err := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
tx.Outputs = []Output{{ require.NoError(t, err)
AssetID: util.Uint256{1, 2, 3, 4}, err = testserdes.DecodeBinary(txBin, new(Transaction))
Amount: 567, require.Error(t, err)
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 TestMarshalUnmarshalJSONInvocationTX(t *testing.T) { func TestMarshalUnmarshalJSONInvocationTX(t *testing.T) {
tx := &Transaction{ tx := &Transaction{
Type: InvocationType, Version: 0,
Version: 3,
Data: &InvocationTX{
Script: []byte{1, 2, 3, 4}, Script: []byte{1, 2, 3, 4},
Gas: util.Fixed8FromFloat(100),
Version: 3,
},
Attributes: []Attribute{}, 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{}, Scripts: []Witness{},
Trimmed: false, Trimmed: false,
} }

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{ base := block.Base{
Version: 0, Version: 0,
PrevHash: util.Uint256{}, 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, Index: 0,
NextConsensus: nextConsensus, NextConsensus: nextConsensus,
Script: transaction.Witness{ Script: transaction.Witness{
InvocationScript: []byte{}, InvocationScript: []byte{},
VerificationScript: []byte{byte(opcode.OLDPUSH1)}, VerificationScript: []byte{byte(opcode.PUSH1)},
},
}
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)},
}, },
} }
b := &block.Block{ b := &block.Block{
Base: base, Base: base,
Transactions: []*transaction.Transaction{ Transactions: []*transaction.Transaction{
&governingTokenTX,
&utilityTokenTX,
issueTx,
deployNativeContracts(), deployNativeContracts(),
}, },
ConsensusData: block.ConsensusData{ ConsensusData: block.ConsensusData{
@ -107,7 +77,7 @@ func deployNativeContracts() *transaction.Transaction {
buf := io.NewBufBinWriter() buf := io.NewBufBinWriter()
emit.Syscall(buf.BinWriter, "Neo.Native.Deploy") emit.Syscall(buf.BinWriter, "Neo.Native.Deploy")
script := buf.Bytes() script := buf.Bytes()
tx := transaction.NewInvocationTX(script, 0) tx := transaction.New(script, 0)
tx.Nonce = 0 tx.Nonce = 0
tx.Sender = hash.Hash160([]byte{byte(opcode.PUSH1)}) tx.Sender = hash.Hash160([]byte{byte(opcode.PUSH1)})
tx.Scripts = []transaction.Witness{ tx.Scripts = []transaction.Witness{
@ -119,45 +89,6 @@ func deployNativeContracts() *transaction.Transaction {
return tx 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) { func getValidators(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) {
validators := make([]*keys.PublicKey, len(cfg.StandbyValidators)) validators := make([]*keys.PublicKey, len(cfg.StandbyValidators))
for i, pubKeyStr := range 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. // have been changed. Consequently, hash of the genesis block has been changed.
// Update expected genesis block hash for better times. // Update expected genesis block hash for better times.
// Old hash is "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf" // Old hash is "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"
expect := "1d4156d233220b893797a684fbb827bb2163b5042edd10653bbc1b2769adbb8d" expect := "e4cfc549c87d4ab7b570c368d05853ffb70eb9ef0f7d9c7a2e6e9e5d713ebbf4"
assert.Equal(t, expect, block.Hash().StringLE()) 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, consensusScript, script.String())
assert.Equal(t, consensusAddr, address.Uint160ToString(script)) 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 ( import (
"github.com/nspcc-dev/neo-go/pkg/interop/account" "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/block"
"github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/header" "github.com/nspcc-dev/neo-go/pkg/interop/header"
@ -73,11 +72,3 @@ func GetAccount(scriptHash []byte) account.Account {
func GetValidators() [][]byte { func GetValidators() [][]byte {
return nil 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 ( import (
"github.com/nspcc-dev/neo-go/pkg/interop/attribute" "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" "github.com/nspcc-dev/neo-go/pkg/interop/witness"
) )
@ -21,22 +19,6 @@ func GetHash(t Transaction) []byte {
return nil 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 // GetAttributes returns a slice of attributes for agiven transaction. Refer to
// attribute package on how to use them. This function uses // attribute package on how to use them. This function uses
// `Neo.Transaction.GetAttributes` syscall. // `Neo.Transaction.GetAttributes` syscall.
@ -44,42 +26,6 @@ func GetAttributes(t Transaction) []attribute.Attribute {
return []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 // GetWitnesses returns a slice of witnesses of a given Transaction. Refer to
// witness package on how to use them. This function uses // witness package on how to use them. This function uses
// `Neo.Transaction.GetWitnesses` syscall. // `Neo.Transaction.GetWitnesses` syscall.

View file

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

View file

@ -11,12 +11,9 @@ import (
"sync" "sync"
"time" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpc/request" "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/rpc/response"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -45,13 +42,6 @@ type Client struct {
// All values are optional. If any duration is not specified // All values are optional. If any duration is not specified
// a default of 4 seconds will be used. // a default of 4 seconds will be used.
type Options struct { 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 // Cert is a client-side certificate, it doesn't work at the moment along
// with the other two options below. // with the other two options below.
Cert string Cert string
@ -107,9 +97,6 @@ func New(ctx context.Context, endpoint string, opts Options) (*Client, error) {
wifMu: new(sync.Mutex), wifMu: new(sync.Mutex),
endpoint: url, endpoint: url,
} }
if opts.Balancer == nil {
opts.Balancer = cl
}
cl.opts = opts cl.opts = opts
cl.requestF = cl.makeHTTPRequest cl.requestF = cl.makeHTTPRequest
return cl, nil return cl, nil
@ -140,27 +127,6 @@ func (c *Client) SetWIF(wif string) error {
return nil 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 { func (c *Client) performRequest(method string, p request.RawParams, v interface{}) error {
var r = request.Raw{ var r = request.Raw{
JSONRPC: request.JSONRPCVersion, JSONRPC: request.JSONRPCVersion,

View file

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

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"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/client"
) )
@ -23,11 +24,16 @@ func Example() {
os.Exit(1) os.Exit(1)
} }
resp, err := c.GetAccountState("ATySFJAbLW7QHsZGHScLhxq6EyNBxx3eFP") addr, err := address.StringToUint160("ATySFJAbLW7QHsZGHScLhxq6EyNBxx3eFP")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) 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) 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" "errors"
"fmt" "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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io" "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/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "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/emit"
@ -94,22 +92,23 @@ func (c *Client) NEP5TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) {
return wallet.NewToken(tokenHash, name, symbol, decimals), nil return wallet.NewToken(tokenHash, name, symbol, decimals), nil
} }
// TransferNEP5 creates an invocation transaction that invokes 'transfer' method // CreateNEP5TransferTx creates an invocation transaction for the 'transfer'
// on a given token to move specified amount of NEP5 assets (in FixedN format // method of a given contract (token) to move specified amount of NEP5 assets
// using contract's number of decimals) to given account. // (in FixedN format using contract's number of decimals) to given account and
func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *wallet.Token, amount int64, gas util.Fixed8) (util.Uint256, error) { // 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) from, err := address.StringToUint160(acc.Address)
if err != nil { 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 // Note: we don't use invoke function here because it requires
// 2 round trips instead of one. // 2 round trips instead of one.
w := io.NewBufBinWriter() 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) emit.Opcode(w.BinWriter, opcode.ASSERT)
script := w.Bytes() script := w.Bytes()
tx := transaction.NewInvocationTX(script, gas) tx := transaction.New(script, gas)
tx.Sender = from tx.Sender = from
tx.Cosigners = []transaction.Cosigner{ 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)) result, err := c.InvokeScript(hex.EncodeToString(script))
if err != nil { 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) gasConsumed, err := util.Fixed8FromString(result.GasConsumed)
if err != nil { 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 { if gasConsumed > 0 {
tx.SystemFee = gasConsumed tx.SystemFee = gasConsumed
@ -134,16 +133,25 @@ func (c *Client) TransferNEP5(acc *wallet.Account, to util.Uint160, token *walle
tx.ValidUntilBlock, err = c.CalculateValidUntilBlock() tx.ValidUntilBlock, err = c.CalculateValidUntilBlock()
if err != nil { if err != nil {
return util.Uint256{}, fmt.Errorf("can't calculate validUntilBlock: %v", err) return nil, 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)
} }
err = c.AddNetworkFee(tx, acc) err = c.AddNetworkFee(tx, acc)
if err != nil { 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 { if err := acc.SignTx(tx); err != nil {

View file

@ -2,6 +2,7 @@ package client
import ( import (
"encoding/hex" "encoding/hex"
"strconv"
"github.com/nspcc-dev/neo-go/pkg/core" "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/block"
@ -16,18 +17,6 @@ import (
"github.com/pkg/errors" "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. // GetApplicationLog returns the contract log based on the specified txid.
func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, error) { func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, error) {
var ( var (
@ -40,18 +29,6 @@ func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, e
return resp, nil 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. // GetBestBlockHash returns the hash of the tallest block in the main chain.
func (c *Client) GetBestBlockHash() (util.Uint256, error) { func (c *Client) GetBestBlockHash() (util.Uint256, error) {
var resp = util.Uint256{} var resp = util.Uint256{}
@ -187,16 +164,6 @@ func (c *Client) GetBlockSysFee(index uint32) (util.Fixed8, error) {
return resp, nil return resp, nil
} }
// GetClaimable returns tx outputs which can be claimed.
func (c *Client) GetClaimable(address string) (*result.ClaimableInfo, error) {
params := request.NewRawParams(address)
resp := new(result.ClaimableInfo)
if err := c.performRequest("getclaimable", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetConnectionCount returns the current number of connections for the node. // GetConnectionCount returns the current number of connections for the node.
func (c *Client) GetConnectionCount() (int, error) { func (c *Client) GetConnectionCount() (int, error) {
var ( var (
@ -329,41 +296,20 @@ func (c *Client) GetTransactionHeight(hash util.Uint256) (uint32, error) {
return resp, nil return resp, nil
} }
// GetTxOut returns the corresponding unspent transaction output information (returned change), // GetUnclaimedGas returns unclaimed GAS amount for the specified address.
// based on the specified hash and index. func (c *Client) GetUnclaimedGas(address string) (util.Fixed8, error) {
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) {
var ( var (
params = request.NewRawParams(address) params = request.NewRawParams(address)
resp = &result.Unclaimed{} resp string
) )
if err := c.performRequest("getunclaimed", params, resp); err != nil { if err := c.performRequest("getunclaimedgas", params, &resp); err != nil {
return nil, err return 0, err
} }
return resp, nil i, err := strconv.ParseInt(resp, 10, 64)
if err != nil {
return 0, err
} }
return util.Fixed8(i), 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
}
return resp, nil
} }
// GetValidators returns the current NEO consensus nodes information and voting status. // 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 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 // SignAndPushInvocationTx signs and pushes given script as an invocation
// transaction using given wif to sign it and spending the amount of gas // 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. // 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 txHash util.Uint256
var err error var err error
tx := transaction.NewInvocationTX(script, sysfee) tx := transaction.New(script, sysfee)
tx.SystemFee = sysfee tx.SystemFee = sysfee
validUntilBlock, err := c.CalculateValidUntilBlock() validUntilBlock, err := c.CalculateValidUntilBlock()
@ -525,14 +438,6 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys
} }
tx.Sender = addr 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) err = c.AddNetworkFee(tx, acc)
if err != nil { if err != nil {
return txHash, errors.Wrapf(err, "failed to add network fee") return txHash, errors.Wrapf(err, "failed to add network fee")

View file

@ -3,7 +3,6 @@ package client
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
@ -11,14 +10,15 @@ import (
"time" "time"
"github.com/gorilla/websocket" "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/block"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/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/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "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/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -31,113 +31,60 @@ type rpcClientTestCase struct {
check func(t *testing.T, c *Client, result interface{}) check func(t *testing.T, c *Client, result interface{})
} }
// getResultBlock202 returns data for block number 1 which is used by several tests. const hexB1 = "00000000e862e7907fc987cd58ddb3abb754aeb8812c9377c45e737a036fe88a622c3b8f301f2e84a86b207270830e7929530ccb841a3df7379fe6f0ac8865b33316839501cdd0847201000001000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c40ab634ce91590e77b246cee8b204e8a270268ee1ef32434cece73f425a7dbc90f1bed1dbe914edcaa2653167ad170ae10e16a9b2c6b7e0af1f711fb848fbb1b7f0c40232de6ad07ee3846bafa96302d37602349501a556df575e7df0743e45b076d6a0c6c6dd4cad3898f9e8848dd054abd303b229fd12984042f241f0e668f39a0fb0c408b4af43057df189a9d471010b5150bab442040403147c5e502bda38cde3ff8bce803f01245e07e2bfb95d57349c55dcc27e3710b82f2735d0f40eb4342908e330c40cda66f743d4ed8d856f5376953f9169581c668a9370245aef16202ebef9bb3f7f81234be62ec287d701ad7d8bf5042648019af9fe5baa0a8e05d279bfdb1d4c994130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb030057040000000000000002000000abec5362f11e75b6e02e407bb98d63675d14384100000000000000003e5f0d0000000000b00400000001abec5362f11e75b6e02e407bb98d63675d14384101590218ddf5050c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c14897720d8cd76f4f00abfa37c0edd889c208fde9b41627d5b523801fd08010c402d96d8fde4ba266f89bc71ef117361967e0d11ed84cd60942a27bc99994dc58adf36a0e74ce976aac657a87a3c19c38e8ca450c67420046b81d98c60fd8feb040c40b3c15d5d23e0403a36cf559caee2979ca6ef00fe255df0e5c3daac4da051016b41eba42668934cd3308359451bafdd5419d059179fd40859684a3b91388bf9d80c407ac048cf8540b091955a374a0f36dae560c92c0134886507a589edf58b9dfbb4e3dbd5450be34e269d2e5454eb14eb7d6280d6101b4529410f829d37634849be0c403bba4113a687ff8507c1753f8519557531cf9df51ecc20deeb2c2b003ec5a1f7588cdd50b99e40b4f8039bb56c5df7ec9e7d6ea4b02fe23792510da21c7557f394130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb0003000000abec5362f11e75b6e02e407bb98d63675d1438410000000000000000de6e0d0000000000b00400000001abec5362f11e75b6e02e407bb98d63675d143841015d0300e87648170000000c14316e851039019d39dfc2c37d6c3fee19fd5809870c14abec5362f11e75b6e02e407bb98d63675d14384113c00c087472616e736665720c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b523801fd08010c4063fb12aa9f3fb83f6324ea9c7ec11fa9e995b51140f480409d68cf4d625e598b0632d6610602984bfa2f5e5ea9bcc62a0e6d818dd271b38530c0d1b8a71b4e0c0c4013e091eac6f304668d647c5c032fd1020597ea5204545e21c38655a6343d58492118f1231ede91af848af7e1d987d1a8816966f5fc1a7821c6c6f62734267bde0c40daadd04a7a4141d96c58de2d373e672ca071e2b82138ef52df016ac522710385db2ac73743d2fe73061fa5d6cb0ff73a7ec7f0667e4c8bff6aa0d5783128d36e0c40dab85cd87d3f92be9532292bdc6f420b0ecbf2f877c70c6a9921ee0fc900dfc53998cf020a51fa9af3d0608f6a2b9048cea3c0b586485802bbd278b261eee8a494130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb"
func getResultBlock202() *result.Block {
nextBlockHash, err := util.Uint256DecodeStringLE("13283c93aec07dc90be3ddd65e2de15e9212f1b3205303f688d6df85129f6b22") 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 { if err != nil {
panic(err) panic(err)
} }
prevBlockHash, err := util.Uint256DecodeStringLE("93b540424c1173e487a47582a652686b2885f959ffd895b30e184842403990ef") b := new(block.Block)
err = testserdes.DecodeBinary(binB, b)
if err != nil { if err != nil {
panic(err) panic(err)
} }
merkleRoot, err := util.Uint256DecodeStringLE("b8e923148dede20901d6fb225579f6d430cc58f24461d1b0f860ee32abbfcc8d") b2Hash, err := util.Uint256DecodeStringLE("f2afe371a27c9dbac4f4a8ad8eba750898b7c04aa298e64fe9e488e947976045")
if err != nil { if err != nil {
panic(err) panic(err)
} }
invScript, err := hex.DecodeString("0c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c3123996") return &result.Block{
if err != nil { Block: b,
panic(err) BlockMetadata: result.BlockMetadata{
} Size: 1681,
verifScript, err := hex.DecodeString("130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb") NextBlockHash: &b2Hash,
if err != nil { Confirmations: 6,
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,
},
} }
var nonce uint64 func getTxMoveNeo() *result.TransactionOutputRaw {
i, err := fmt.Sscanf("0000000000000457", "%016x", &nonce) b1 := getResultBlock1()
if i != 1 { txBin, err := hex.DecodeString(hexTxMoveNeo)
panic("can't decode nonce")
}
if err != nil { if err != nil {
panic(err) panic(err)
} }
nextCon, err := address.StringToUint160("AXSvJVzydxXuL9da4GVwK25zdesCrVKkHL") tx := new(transaction.Transaction)
err = testserdes.DecodeBinary(txBin, tx)
if err != nil { if err != nil {
panic(err) panic(err)
} }
blck := &block.Block{ return &result.TransactionOutputRaw{
Base: block.Base{ Transaction: tx,
Version: 0, TransactionMetadata: result.TransactionMetadata{
PrevHash: prevBlockHash, Timestamp: b1.Timestamp,
MerkleRoot: merkleRoot, Blockhash: b1.Block.Hash(),
Timestamp: 1589300496, Confirmations: int(b1.Confirmations),
Index: 202,
NextConsensus: nextCon,
Script: transaction.Witness{
InvocationScript: invScript,
VerificationScript: verifScript,
},
},
ConsensusData: block.ConsensusData{
PrimaryIndex: 0,
Nonce: nonce,
},
Transactions: []*transaction.Transaction{tx},
}
// Update hashes for correct result comparison.
_ = tx.Hash()
_ = blck.Hash()
return &result.Block{
Block: blck,
BlockMetadata: result.BlockMetadata{
Size: 781,
Confirmations: 6,
NextBlockHash: &nextBlockHash,
}, },
} }
} }
@ -146,32 +93,6 @@ func getResultBlock202() *result.Block {
// published in official C# JSON-RPC API v2.10.3 reference // 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) // (see https://docs.neo.org/docs/en-us/reference/rpc/latest-version/api.html)
var rpcClientTestCases = map[string][]rpcClientTestCase{ 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": { "getapplicationlog": {
{ {
name: "positive", 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": { "getbestblockhash": {
{ {
name: "positive", name: "positive",
@ -248,51 +145,37 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{ {
name: "byIndex_positive", name: "byIndex_positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetBlockByIndex(202) return c.GetBlockByIndex(1)
}, },
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000ef9039404248180eb395d8ff59f985286b6852a68275a487e473114c4240b5938dccbfab32ee60f8b0d16144f258cc30d4f6795522fbd60109e2ed8d1423e9b810cdba5e00000000ca000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c312399694130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb02005704000000000000800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexB1 + `"}`,
result: func(c *Client) interface{} { return &block.Block{} }, result: func(c *Client) interface{} {
check: func(t *testing.T, c *Client, result interface{}) { b := getResultBlock1()
res, ok := result.(*block.Block) return b.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())
}, },
}, },
{ {
name: "byIndex_verbose_positive", name: "byIndex_verbose_positive",
invoke: func(c *Client) (i interface{}, err error) { 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{} { result: func(c *Client) interface{} {
return getResultBlock202() return getResultBlock1()
}, },
}, },
{ {
name: "byHash_positive", name: "byHash_positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint256DecodeStringLE("86fe1061140b2ea791b0739fb9732abc6e5e47de4927228a1ac41de3d93eb7cb") hash, err := util.Uint256DecodeStringLE("d151651e86680a7ecbc87babf3346a42e7bc9974414ce192c9c22ac4f2e9d043")
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.GetBlockByHash(hash) return c.GetBlockByHash(hash)
}, },
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000ef9039404248180eb395d8ff59f985286b6852a68275a487e473114c4240b5938dccbfab32ee60f8b0d16144f258cc30d4f6795522fbd60109e2ed8d1423e9b810cdba5e00000000ca000000abec5362f11e75b6e02e407bb98d63675d14384101fd08010c403620ef8f02d7884c553fb6c54d2fe717cfddd9450886c5fc88a669a29a82fa1a7c715076996567a5a56747f20f10d7e4db071d73b306ccbf17f9a916fcfa1d020c4099e27d87bbb3fb4ce1c77dca85cf3eac46c9c3de87d8022ef7ad2b0a2bb980339293849cf85e5a0a5615ea7bc5bb0a7f28e31f278dc19d628f64c49b888df4c60c40616eefc9286843c2f3f2cf1815988356e409b3f10ffaf60b3468dc0a92dd929cbc8d5da74052c303e7474412f6beaddd551e9056c4e7a5fccdc06107e48f3fe10c40fd2d25d4156e969345c0522669b509e5ced70e4265066eadaf85eea3919d5ded525f8f52d6f0dfa0186c964dd0302fca5bc2dc0540b4ed21085be478c312399694130c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e0c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd620c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699140b413073b3bb02005704000000000000800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexB1 + `"}`,
result: func(c *Client) interface{} { return &block.Block{} }, result: func(c *Client) interface{} {
check: func(t *testing.T, c *Client, result interface{}) { b := getResultBlock1()
res, ok := result.(*block.Block) return b.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())
}, },
}, },
{ {
@ -304,9 +187,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
} }
return c.GetBlockByHashVerbose(hash) 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{} { result: func(c *Client) interface{} {
return getResultBlock202() return getResultBlock1()
}, },
}, },
}, },
@ -348,15 +231,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
} }
return c.GetBlockHeader(hash) return c.GetBlockHeader(hash)
}, },
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"00000000d039da5e49d63eb0533437d24ff8ceb6aeacf88680599c39f0ffca8948dfcdb94a3def1fca91cf45d69358414e3be77f7621e557f4cebbdb79a47d3cf56ac007f920a05e0000000001000000d60ac443bb800fb08261e75fa5925d747d48586101fd04014055041db6a59c99ab98137cc57e1e56a0a89856a311b2d2fc0aec76ec714c7616edc8fc5c9b81b27f25b7db1a61f64be0730a9cc103efcea1195cc3fe55843e264027e49c647f48bb08d3c32b79ee3432005ea577d7e497f78b46f1e81858848f961b557fb42a92e8eb4433fed203c917cbebb2138a31ed86750fb769d1e70956c0404c20054aa8bd45b520cba9410a9dd6c256481066bb657d7793fbba5551898c91b6dde81285fac841753ccfdd3193d08f19d5431313fa0d926ca965072a5fa3384026b0705078409bcc62fb98bb985edc387edeaaeba37bb7642d88a90762b2c2a62d9b61d53c097d548a368e450c4d995a178d5af28d4c93698233c52de05e3f0094534c2102103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e4c2102a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd624c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc24c2103d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee6995450683073b3bb00"}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexHeader1 + `"}`,
result: func(c *Client) interface{} { return &block.Header{} }, result: func(c *Client) interface{} {
check: func(t *testing.T, c *Client, result interface{}) { b := getResultBlock1()
res, ok := result.(*block.Header) return b.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())
}, },
}, },
{ {
@ -368,12 +246,12 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
} }
return c.GetBlockHeaderVerbose(hash) 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{} { result: func(c *Client) interface{} {
b := getResultBlock202() b := getResultBlock1()
return &result.Header{ return &result.Header{
Hash: b.Hash(), Hash: b.Hash(),
Size: 781, Size: 518,
Version: b.Version, Version: b.Version,
NextBlockHash: b.NextBlockHash, NextBlockHash: b.NextBlockHash,
PrevBlockHash: b.PrevHash, 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": { "getconnectioncount": {
{ {
name: "positive", name: "positive",
@ -593,21 +441,16 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{ {
name: "positive", name: "positive",
invoke: func(c *Client) (i interface{}, err error) { invoke: func(c *Client) (i interface{}, err error) {
hash, err := util.Uint256DecodeStringLE("8185b0db7ed77190b93ac8bd44896822cd8f3cfcf702b3f50131e0efd200ef96") hash, err := util.Uint256DecodeStringLE("ca23bd5df3249836849309ca2afe972bfd288b0a7ae61302c8fd545daa8bffd6")
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.GetRawTransaction(hash) return c.GetRawTransaction(hash)
}, },
serverResponse: `{"id":1,"jsonrpc":"2.0","result":"800003000000316e851039019d39dfc2c37d6c3fee19fd58098700000000000000000000000000000000b004000000000170f3b507ea9eae15ebb7f10195c1239bbaa12ca996ff070e4a8501131045e033000001787cc0a786adfe829bc2dffc5637e6855c0a82e02deee97dedbc2aac3e0e5e1a00184a27db862300316e851039019d39dfc2c37d6c3fee19fd58098701420c402caebbee911a1f159aa05ab40093d086090a817e837f3f87e8b3e47f6b083649137770f6dda0349ddd611bc47402aca457a89b3b7b0076307ab6a47fd57048eb290c2102b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc20b410a906ad4"}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":"` + hexTxMoveNeo + `"}`,
result: func(c *Client) interface{} { return &transaction.Transaction{} }, result: func(c *Client) interface{} {
check: func(t *testing.T, c *Client, result interface{}) { tx := getTxMoveNeo()
res, ok := result.(*transaction.Transaction) return tx.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)
}, },
}, },
{ {
@ -619,68 +462,9 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
} }
return c.GetRawTransactionVerbose(hash) 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{} { result: func(c *Client) interface{} {
blockHash, err := util.Uint256DecodeStringLE("cbb73ed9e31dc41a8a222749de475e6ebc2a73b99f73b091a72e0b146110fe86") return getTxMoveNeo()
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),
},
}
}, },
}, },
}, },
@ -724,56 +508,15 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}, },
}, },
}, },
"gettxout": { "getunclaimedgas": {
{ {
name: "positive", name: "positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint256DecodeStringLE("f4250dab094c38d8265acc15c366dc508d2e14bf5699e12d9df26577ed74d657") return c.GetUnclaimedGas("AGofsxAUDwt52KjaB664GYsqVAkULYvKNt")
if err != nil {
panic(err)
}
return c.GetTxOut(hash, 0)
}, },
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{} { result: func(c *Client) interface{} {
return &result.TransactionOutput{ return util.Fixed8(897299680935)
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))
}, },
}, },
}, },
@ -873,7 +616,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{ {
name: "positive", name: "positive",
invoke: func(c *Client) (interface{}, error) { 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}`, serverResponse: `{"jsonrpc":"2.0","id":1,"result":true}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
@ -994,7 +737,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{ {
name: "sendrawtransaction_bad_server_answer", name: "sendrawtransaction_bad_server_answer",
invoke: func(c *Client) (interface{}, error) { 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"}}`: { `{"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", name: "getapplicationlog_invalid_params_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetApplicationLog(util.Uint256{}) 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", name: "getbestblockhash_invalid_params_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
@ -1087,12 +818,6 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
return c.GetBlockSysFee(1) return c.GetBlockSysFee(1)
}, },
}, },
{
name: "getclaimable_invalid_params_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetClaimable("")
},
},
{ {
name: "getconnectioncount_invalid_params_error", name: "getconnectioncount_invalid_params_error",
invoke: func(c *Client) (interface{}, 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) { invoke: func(c *Client) (interface{}, error) {
return c.GetTxOut(util.Uint256{}, 0) return c.GetUnclaimedGas("")
},
},
{
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("")
}, },
}, },
{ {
@ -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", name: "getapplicationlog_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetApplicationLog(util.Uint256{}) return c.GetApplicationLog(util.Uint256{})
}, },
}, },
{
name: "getassetstate_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetAssetState(core.GoverningTokenID())
},
},
{ {
name: "getbestblockhash_unmarshalling_error", name: "getbestblockhash_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
@ -1269,12 +970,6 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
return c.GetBlockSysFee(1) return c.GetBlockSysFee(1)
}, },
}, },
{
name: "getclaimable_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) {
return c.GetClaimable("")
},
},
{ {
name: "getconnectioncount_unmarshalling_error", name: "getconnectioncount_unmarshalling_error",
invoke: func(c *Client) (interface{}, 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) { invoke: func(c *Client) (interface{}, error) {
return c.GetTxOut(util.Uint256{}, 0) return c.GetUnclaimedGas("")
},
},
{
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("")
}, },
}, },
{ {
@ -1380,7 +1063,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{ {
name: "sendrawtransaction_unmarshalling_error", name: "sendrawtransaction_unmarshalling_error",
invoke: func(c *Client) (interface{}, 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{ 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":"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":"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":"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":"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":"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":[]}`, `{"jsonrpc":"2.0","method":"event_missed","params":[]}`,
} }
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

View file

@ -5,77 +5,14 @@ import (
"fmt" "fmt"
"strconv" "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/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/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "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/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "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 // DetailsToSCProperties extract the fields needed from ContractDetails
// and converts them to smartcontract.PropertyState. // and converts them to smartcontract.PropertyState.
func DetailsToSCProperties(contract *smartcontract.ContractDetails) 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, Block: b,
BlockMetadata: BlockMetadata{ BlockMetadata: BlockMetadata{
Size: io.GetVarSize(b), 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 { func NewTransactionOutputRaw(tx *transaction.Transaction, header *block.Header, chain blockchainer.Blockchainer) TransactionOutputRaw {
// confirmations formula // confirmations formula
confirmations := int(chain.BlockHeight() - header.Base.Index + 1) confirmations := int(chain.BlockHeight() - header.Base.Index + 1)
// set index position
for i, o := range tx.Outputs {
o.Position = i
}
return TransactionOutputRaw{ return TransactionOutputRaw{
Transaction: tx, Transaction: tx,
TransactionMetadata: TransactionMetadata{ TransactionMetadata: TransactionMetadata{

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