2022-07-21 19:39:53 +00:00
package rpcclient
2021-03-23 19:50:15 +00:00
import (
"crypto/elliptic"
"errors"
"fmt"
2022-07-06 15:15:17 +00:00
"github.com/nspcc-dev/neo-go/pkg/config"
2022-06-15 18:23:29 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
2021-03-23 19:50:15 +00:00
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
2022-06-15 18:23:29 +00:00
"github.com/nspcc-dev/neo-go/pkg/io"
2022-07-22 16:09:29 +00:00
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
2022-07-21 19:39:53 +00:00
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
2022-06-15 18:23:29 +00:00
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
2021-03-23 19:04:34 +00:00
"github.com/nspcc-dev/neo-go/pkg/util"
2022-06-15 18:23:29 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
2021-03-23 19:50:15 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
2022-04-20 18:30:09 +00:00
// getInvocationError returns an error in case of bad VM state or an empty stack.
2021-03-23 19:50:15 +00:00
func getInvocationError ( result * result . Invoke ) error {
if result . State != "HALT" {
return fmt . Errorf ( "invocation failed: %s" , result . FaultException )
}
if len ( result . Stack ) == 0 {
return errors . New ( "result stack is empty" )
}
return nil
}
2022-04-20 18:30:09 +00:00
// topBoolFromStack returns the top boolean value from the stack.
2021-03-23 19:50:15 +00:00
func topBoolFromStack ( st [ ] stackitem . Item ) ( bool , error ) {
index := len ( st ) - 1 // top stack element is last in the array
result , ok := st [ index ] . Value ( ) . ( bool )
if ! ok {
return false , fmt . Errorf ( "invalid stack item type: %s" , st [ index ] . Type ( ) )
}
return result , nil
}
2022-04-20 18:30:09 +00:00
// topIntFromStack returns the top integer value from the stack.
2021-03-23 19:50:15 +00:00
func topIntFromStack ( st [ ] stackitem . Item ) ( int64 , error ) {
index := len ( st ) - 1 // top stack element is last in the array
bi , err := st [ index ] . TryInteger ( )
if err != nil {
return 0 , err
}
return bi . Int64 ( ) , nil
}
2022-04-20 18:30:09 +00:00
// topPublicKeysFromStack returns the top array of public keys from the stack.
2021-03-23 19:50:15 +00:00
func topPublicKeysFromStack ( st [ ] stackitem . Item ) ( keys . PublicKeys , error ) {
index := len ( st ) - 1 // top stack element is last in the array
var (
pks keys . PublicKeys
err error
)
items , ok := st [ index ] . Value ( ) . ( [ ] stackitem . Item )
if ! ok {
return nil , fmt . Errorf ( "invalid stack item type: %s" , st [ index ] . Type ( ) )
}
pks = make ( keys . PublicKeys , len ( items ) )
for i , item := range items {
val , ok := item . Value ( ) . ( [ ] byte )
if ! ok {
return nil , fmt . Errorf ( "invalid array element #%d: %s" , i , item . Type ( ) )
}
pks [ i ] , err = keys . NewPublicKeyFromBytes ( val , elliptic . P256 ( ) )
if err != nil {
return nil , err
}
}
return pks , nil
}
2022-04-20 18:30:09 +00:00
// top string from stack returns the top string from the stack.
2021-03-23 19:50:15 +00:00
func topStringFromStack ( st [ ] stackitem . Item ) ( string , error ) {
index := len ( st ) - 1 // top stack element is last in the array
bs , err := st [ index ] . TryBytes ( )
if err != nil {
return "" , err
}
return string ( bs ) , nil
}
2021-03-23 19:04:34 +00:00
2022-04-20 18:30:09 +00:00
// topUint160FromStack returns the top util.Uint160 from the stack.
2021-03-23 19:04:34 +00:00
func topUint160FromStack ( st [ ] stackitem . Item ) ( util . Uint160 , error ) {
index := len ( st ) - 1 // top stack element is last in the array
bs , err := st [ index ] . TryBytes ( )
if err != nil {
return util . Uint160 { } , err
}
return util . Uint160DecodeBytesBE ( bs )
}
2022-04-20 18:30:09 +00:00
// topMapFromStack returns the top stackitem.Map from the stack.
2021-03-23 19:04:34 +00:00
func topMapFromStack ( st [ ] stackitem . Item ) ( * stackitem . Map , error ) {
index := len ( st ) - 1 // top stack element is last in the array
if t := st [ index ] . Type ( ) ; t != stackitem . MapT {
return nil , fmt . Errorf ( "invalid return stackitem type: %s" , t . String ( ) )
}
return st [ index ] . ( * stackitem . Map ) , nil
}
2021-04-28 14:47:44 +00:00
2022-06-15 18:23:29 +00:00
// InvokeAndPackIteratorResults creates a script containing System.Contract.Call
// of the specified contract with the specified arguments. It assumes that the
// specified operation will return iterator. The script traverses the resulting
// iterator, packs all its values into array and pushes the resulting array on
// stack. Constructed script is invoked via `invokescript` JSON-RPC API using
// the provided signers. The result of the script invocation contains single array
// stackitem on stack if invocation HALTed. InvokeAndPackIteratorResults can be
// used to interact with JSON-RPC server where iterator sessions are disabled to
2022-07-06 15:15:17 +00:00
// retrieve iterator values via single `invokescript` JSON-RPC call. It returns
// maxIteratorResultItems items at max which is set to
// config.DefaultMaxIteratorResultItems by default.
func ( c * Client ) InvokeAndPackIteratorResults ( contract util . Uint160 , operation string , params [ ] smartcontract . Parameter , signers [ ] transaction . Signer , maxIteratorResultItems ... int ) ( * result . Invoke , error ) {
max := config . DefaultMaxIteratorResultItems
if len ( maxIteratorResultItems ) != 0 {
max = maxIteratorResultItems [ 0 ]
}
bytes , err := createIteratorUnwrapperScript ( contract , operation , params , max )
2022-06-15 18:23:29 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to create iterator unwrapper script: %w" , err )
}
return c . InvokeScript ( bytes , signers )
}
2022-07-06 15:15:17 +00:00
func createIteratorUnwrapperScript ( contract util . Uint160 , operation string , params [ ] smartcontract . Parameter , maxIteratorResultItems int ) ( [ ] byte , error ) {
2022-06-15 18:23:29 +00:00
script := io . NewBufBinWriter ( )
2022-07-06 15:15:17 +00:00
emit . Int ( script . BinWriter , int64 ( maxIteratorResultItems ) )
2022-06-15 18:23:29 +00:00
// Pack arguments for System.Contract.Call.
arr , err := smartcontract . ExpandParameterToEmitable ( smartcontract . Parameter {
Type : smartcontract . ArrayType ,
Value : params ,
} )
if err != nil {
return nil , fmt . Errorf ( "failed to expand parameters array to emitable: %w" , err )
}
emit . Array ( script . BinWriter , arr . ( [ ] interface { } ) ... )
emit . AppCallNoArgs ( script . BinWriter , contract , operation , callflag . All ) // The System.Contract.Call itself, it will push Iterator on estack.
emit . Opcodes ( script . BinWriter , opcode . NEWARRAY0 ) // Push new empty array to estack. This array will store iterator's elements.
// Start the iterator traversal cycle.
iteratorTraverseCycleStartOffset := script . Len ( )
emit . Opcodes ( script . BinWriter , opcode . OVER ) // Load iterator from 1-st cell of estack.
emit . Syscall ( script . BinWriter , interopnames . SystemIteratorNext ) // Call System.Iterator.Next, it will pop the iterator from estack and push `true` or `false` to estack.
jmpIfNotOffset := script . Len ( )
emit . Instruction ( script . BinWriter , opcode . JMPIFNOT , // Pop boolean value (from the previous step) from estack, if `false`, then iterator has no more items => jump to the end of program.
[ ] byte {
0x00 , // jump to loadResultOffset, but we'll fill this byte after script creation.
} )
emit . Opcodes ( script . BinWriter , opcode . DUP , // Duplicate the resulting array from 0-th cell of estack and push it to estack.
opcode . PUSH2 , opcode . PICK ) // Pick iterator from the 2-nd cell of estack.
emit . Syscall ( script . BinWriter , interopnames . SystemIteratorValue ) // Call System.Iterator.Value, it will pop the iterator from estack and push its current value to estack.
emit . Opcodes ( script . BinWriter , opcode . APPEND ) // Pop iterator value and the resulting array from estack. Append value to the resulting array. Array is a reference type, thus, value stored at the 1-th cell of local slot will also be updated.
2022-07-06 15:15:17 +00:00
emit . Opcodes ( script . BinWriter , opcode . DUP , // Duplicate the resulting array from 0-th cell of estack and push it to estack.
opcode . SIZE , // Pop array from estack and push its size to estack.
opcode . PUSH3 , opcode . PICK , // Pick maxIteratorResultItems from the 3-d cell of estack.
opcode . GE ) // Compare len(arr) and maxIteratorResultItems
jmpIfMaxReachedOffset := script . Len ( )
emit . Instruction ( script . BinWriter , opcode . JMPIF , // Pop boolean value (from the previous step) from estack, if `false`, then max array elements is reached => jump to the end of program.
[ ] byte {
0x00 , // jump to loadResultOffset, but we'll fill this byte after script creation.
} )
2022-06-15 18:23:29 +00:00
jmpOffset := script . Len ( )
emit . Instruction ( script . BinWriter , opcode . JMP , // Jump to the start of iterator traverse cycle.
[ ] byte {
uint8 ( iteratorTraverseCycleStartOffset - jmpOffset ) , // jump to iteratorTraverseCycleStartOffset; offset is relative to JMP position.
} )
// End of the program: push the result on stack and return.
loadResultOffset := script . Len ( )
2022-07-06 15:15:17 +00:00
emit . Opcodes ( script . BinWriter , opcode . NIP , // Remove iterator from the 1-st cell of estack
opcode . NIP ) // Remove maxIteratorResultItems from the 1-st cell of estack, so that only resulting array is left on estack.
2022-06-15 18:23:29 +00:00
if err := script . Err ; err != nil {
return nil , fmt . Errorf ( "failed to build iterator unwrapper script: %w" , err )
}
// Fill in JMPIFNOT instruction parameter.
bytes := script . Bytes ( )
bytes [ jmpIfNotOffset + 1 ] = uint8 ( loadResultOffset - jmpIfNotOffset ) // +1 is for JMPIFNOT itself; offset is relative to JMPIFNOT position.
2022-07-06 15:15:17 +00:00
// Fill in jmpIfMaxReachedOffset instruction parameter.
bytes [ jmpIfMaxReachedOffset + 1 ] = uint8 ( loadResultOffset - jmpIfMaxReachedOffset ) // +1 is for JMPIF itself; offset is relative to JMPIF position.
2022-06-15 18:23:29 +00:00
return bytes , nil
}
// topIterableFromStack returns the list of elements of `resultItemType` type from the top element
// of the provided stack. The top element is expected to be an Array, otherwise an error is returned.
2021-04-28 14:47:44 +00:00
func topIterableFromStack ( st [ ] stackitem . Item , resultItemType interface { } ) ( [ ] interface { } , error ) {
2022-06-15 18:23:29 +00:00
index := len ( st ) - 1 // top stack element is the last in the array
if t := st [ index ] . Type ( ) ; t != stackitem . ArrayT {
return nil , fmt . Errorf ( "invalid return stackitem type: %s (Array expected)" , t . String ( ) )
2021-04-28 14:47:44 +00:00
}
2022-06-15 18:23:29 +00:00
items , ok := st [ index ] . Value ( ) . ( [ ] stackitem . Item )
2021-04-28 14:47:44 +00:00
if ! ok {
2022-06-15 18:23:29 +00:00
return nil , fmt . Errorf ( "failed to deserialize iterable from Array stackitem: invalid value type (Array expected)" )
2021-04-28 14:47:44 +00:00
}
2022-06-15 18:23:29 +00:00
result := make ( [ ] interface { } , len ( items ) )
for i := range items {
2021-04-28 14:47:44 +00:00
switch resultItemType . ( type ) {
2022-02-09 08:55:07 +00:00
case [ ] byte :
2022-06-15 18:23:29 +00:00
bytes , err := items [ i ] . TryBytes ( )
2022-02-09 08:55:07 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to deserialize []byte from stackitem #%d: %w" , i , err )
}
result [ i ] = bytes
2021-04-28 14:47:44 +00:00
case string :
2022-06-15 18:23:29 +00:00
bytes , err := items [ i ] . TryBytes ( )
2021-04-28 14:47:44 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to deserialize string from stackitem #%d: %w" , i , err )
}
result [ i ] = string ( bytes )
case util . Uint160 :
2022-06-15 18:23:29 +00:00
bytes , err := items [ i ] . TryBytes ( )
2021-04-28 14:47:44 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to deserialize uint160 from stackitem #%d: %w" , i , err )
}
result [ i ] , err = util . Uint160DecodeBytesBE ( bytes )
if err != nil {
return nil , fmt . Errorf ( "failed to decode uint160 from stackitem #%d: %w" , i , err )
}
2021-09-09 19:52:27 +00:00
case nns . RecordState :
2022-06-15 18:23:29 +00:00
rs , ok := items [ i ] . Value ( ) . ( [ ] stackitem . Item )
2021-09-09 19:52:27 +00:00
if ! ok {
return nil , fmt . Errorf ( "failed to decode RecordState from stackitem #%d: not a struct" , i )
}
if len ( rs ) != 3 {
return nil , fmt . Errorf ( "failed to decode RecordState from stackitem #%d: wrong number of elements" , i )
}
name , err := rs [ 0 ] . TryBytes ( )
if err != nil {
2022-04-20 12:00:12 +00:00
return nil , fmt . Errorf ( "failed to decode RecordState from stackitem #%d: %w" , i , err )
2021-09-09 19:52:27 +00:00
}
typ , err := rs [ 1 ] . TryInteger ( )
if err != nil {
2022-04-20 12:00:12 +00:00
return nil , fmt . Errorf ( "failed to decode RecordState from stackitem #%d: %w" , i , err )
2021-09-09 19:52:27 +00:00
}
data , err := rs [ 2 ] . TryBytes ( )
if err != nil {
2022-04-20 12:00:12 +00:00
return nil , fmt . Errorf ( "failed to decode RecordState from stackitem #%d: %w" , i , err )
2021-09-09 19:52:27 +00:00
}
u64Typ := typ . Uint64 ( )
if ! typ . IsUint64 ( ) || u64Typ > 255 {
2022-04-20 12:00:12 +00:00
return nil , fmt . Errorf ( "failed to decode RecordState from stackitem #%d: bad type" , i )
2021-09-09 19:52:27 +00:00
}
result [ i ] = nns . RecordState {
Name : string ( name ) ,
Type : nns . RecordType ( u64Typ ) ,
Data : string ( data ) ,
}
2021-04-28 14:47:44 +00:00
default :
return nil , errors . New ( "unsupported iterable type" )
}
}
return result , nil
}
2022-07-06 13:55:51 +00:00
// topIteratorFromStack returns the top Iterator from the stack.
func topIteratorFromStack ( st [ ] stackitem . Item ) ( result . Iterator , error ) {
index := len ( st ) - 1 // top stack element is the last in the array
if t := st [ index ] . Type ( ) ; t != stackitem . InteropT {
return result . Iterator { } , fmt . Errorf ( "expected InteropInterface on stack, got %s" , t )
}
iter , ok := st [ index ] . Value ( ) . ( result . Iterator )
if ! ok {
return result . Iterator { } , fmt . Errorf ( "failed to deserialize iterable from interop stackitem: invalid value type (Iterator expected)" )
}
return iter , nil
}