diff --git a/pkg/core/native/ledger.go b/pkg/core/native/ledger.go index e00923a71..64506e506 100644 --- a/pkg/core/native/ledger.go +++ b/pkg/core/native/ledger.go @@ -10,6 +10,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -63,6 +64,11 @@ func newLedger() *Ledger { md = newMethodAndPrice(l.getTransactionFromBlock, 1<<16, callflag.ReadStates) l.AddMethod(md, desc) + desc = newDescriptor("getTransactionSigners", smartcontract.ArrayType, + manifest.NewParameter("hash", smartcontract.Hash256Type)) + md = newMethodAndPrice(l.getTransactionSigners, 1<<15, callflag.ReadStates) + l.AddMethod(md, desc) + desc = newDescriptor("getTransactionVMState", smartcontract.IntegerType, manifest.NewParameter("hash", smartcontract.Hash256Type)) md = newMethodAndPrice(l.getTransactionVMState, 1<<15, callflag.ReadStates) @@ -148,6 +154,15 @@ func (l *Ledger) getTransactionFromBlock(ic *interop.Context, params []stackitem return TransactionToStackItem(block.Transactions[index]) } +// getTransactionSigners returns transaction signers to the SC. +func (l *Ledger) getTransactionSigners(ic *interop.Context, params []stackitem.Item) stackitem.Item { + tx, h, err := getTransactionAndHeight(ic.DAO, params[0]) + if err != nil || !isTraceableBlock(ic.Chain, h) { + return stackitem.Null{} + } + return SignersToStackItem(tx.Signers) +} + // getTransactionVMState returns VM state got after transaction invocation. func (l *Ledger) getTransactionVMState(ic *interop.Context, params []stackitem.Item) stackitem.Item { hash, err := getUint256FromItem(params[0]) @@ -242,3 +257,37 @@ func TransactionToStackItem(t *transaction.Transaction) stackitem.Item { stackitem.NewByteArray(t.Script), }) } + +// SignersToStackItem converts transaction.Signers to stackitem.Item. +func SignersToStackItem(signers []transaction.Signer) stackitem.Item { + res := make([]stackitem.Item, len(signers)) + bw := io.NewBufBinWriter() + for i, s := range signers { + s.EncodeBinary(bw.BinWriter) + if bw.Err != nil { + panic(fmt.Errorf("failed to serialize signer %d to stackitem: %w", i, bw.Err)) + } + contracts := make([]stackitem.Item, len(s.AllowedContracts)) + for j, c := range s.AllowedContracts { + contracts[j] = stackitem.NewByteArray(c.BytesBE()) + } + groups := make([]stackitem.Item, len(s.AllowedGroups)) + for j, g := range s.AllowedGroups { + groups[j] = stackitem.NewByteArray(g.Bytes()) + } + rules := make([]stackitem.Item, len(s.Rules)) + for j, r := range s.Rules { + rules[j] = r.ToStackItem() + } + res[i] = stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(bw.Bytes()), + stackitem.NewByteArray(s.Account.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(s.Scopes))), + stackitem.NewArray(contracts), + stackitem.NewArray(groups), + stackitem.NewArray(rules), + }) + bw.Reset() + } + return stackitem.NewArray(res) +} diff --git a/pkg/core/native/native_test/ledger_test.go b/pkg/core/native/native_test/ledger_test.go index 9306f3008..a7f43b383 100644 --- a/pkg/core/native/native_test/ledger_test.go +++ b/pkg/core/native/native_test/ledger_test.go @@ -6,6 +6,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/util" @@ -172,3 +174,38 @@ func TestLedger_GetBlock(t *testing.T) { ledgerInvoker.Invoke(t, stackitem.Null{}, "getBlock", b.Hash()) }) } + +func TestLedger_GetTransactionSigners(t *testing.T) { + c := newLedgerClient(t) + e := c.Executor + ledgerInvoker := c.WithSigners(c.Committee) + + txHash := ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex") + + t.Run("good", func(t *testing.T) { + s := &transaction.Signer{ + Account: c.CommitteeHash, + Scopes: transaction.Global, + } + bw := io.NewBufBinWriter() + s.EncodeBinary(bw.BinWriter) + require.NoError(t, bw.Err) + expected := stackitem.NewArray([]stackitem.Item{ + stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(bw.Bytes()), + stackitem.NewByteArray(s.Account.BytesBE()), + stackitem.NewBigInteger(big.NewInt(int64(s.Scopes))), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + stackitem.NewArray([]stackitem.Item{}), + }), + }) + ledgerInvoker.Invoke(t, expected, "getTransactionSigners", txHash) + }) + t.Run("unknown transaction", func(t *testing.T) { + ledgerInvoker.Invoke(t, stackitem.Null{}, "getTransactionSigners", util.Uint256{1, 2, 3}) + }) + t.Run("not a hash", func(t *testing.T) { + ledgerInvoker.InvokeFail(t, "expected []byte of size 32", "getTransactionSigners", []byte{1, 2, 3}) + }) +} diff --git a/pkg/core/transaction/witness_condition.go b/pkg/core/transaction/witness_condition.go index cb41b7746..fb8e6ce39 100644 --- a/pkg/core/transaction/witness_condition.go +++ b/pkg/core/transaction/witness_condition.go @@ -3,10 +3,12 @@ package transaction import ( "encoding/json" "errors" + "math/big" "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" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) //go:generate stringer -type=WitnessConditionType -linecomment @@ -50,6 +52,8 @@ type WitnessCondition interface { // DecodeBinarySpecific decodes type-specific binary data from the given // reader (not including type data). DecodeBinarySpecific(*io.BinReader, int) + // ToStackItem converts WitnessCondition to stackitem.Item. + ToStackItem() stackitem.Item json.Marshaler } @@ -129,6 +133,12 @@ func (c *ConditionBoolean) MarshalJSON() ([]byte, error) { return json.Marshal(aux) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c *ConditionBoolean) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), bool(*c)) +} + // Type implements WitnessCondition interface and returns condition type. func (c *ConditionNot) Type() WitnessConditionType { return WitnessNot @@ -166,6 +176,12 @@ func (c *ConditionNot) MarshalJSON() ([]byte, error) { return json.Marshal(aux) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c *ConditionNot) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), c.Condition) +} + // Type implements WitnessCondition interface and returns condition type. func (c *ConditionAnd) Type() WitnessConditionType { return WitnessAnd @@ -242,6 +258,12 @@ func (c *ConditionAnd) MarshalJSON() ([]byte, error) { return arrayToJSON(c, []WitnessCondition(*c)) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c *ConditionAnd) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), []WitnessCondition(*c)) +} + // Type implements WitnessCondition interface and returns condition type. func (c *ConditionOr) Type() WitnessConditionType { return WitnessOr @@ -282,6 +304,12 @@ func (c *ConditionOr) MarshalJSON() ([]byte, error) { return arrayToJSON(c, []WitnessCondition(*c)) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c *ConditionOr) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), []WitnessCondition(*c)) +} + // Type implements WitnessCondition interface and returns condition type. func (c *ConditionScriptHash) Type() WitnessConditionType { return WitnessScriptHash @@ -314,6 +342,12 @@ func (c *ConditionScriptHash) MarshalJSON() ([]byte, error) { return json.Marshal(aux) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c *ConditionScriptHash) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), util.Uint160(*c)) +} + // Type implements WitnessCondition interface and returns condition type. func (c *ConditionGroup) Type() WitnessConditionType { return WitnessGroup @@ -346,6 +380,12 @@ func (c *ConditionGroup) MarshalJSON() ([]byte, error) { return json.Marshal(aux) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c *ConditionGroup) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), keys.PublicKey(*c)) +} + // Type implements WitnessCondition interface and returns condition type. func (c ConditionCalledByEntry) Type() WitnessConditionType { return WitnessCalledByEntry @@ -376,6 +416,12 @@ func (c ConditionCalledByEntry) MarshalJSON() ([]byte, error) { return json.Marshal(aux) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c ConditionCalledByEntry) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), nil) +} + // Type implements WitnessCondition interface and returns condition type. func (c *ConditionCalledByContract) Type() WitnessConditionType { return WitnessCalledByContract @@ -408,6 +454,12 @@ func (c *ConditionCalledByContract) MarshalJSON() ([]byte, error) { return json.Marshal(aux) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c *ConditionCalledByContract) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), util.Uint160(*c)) +} + // Type implements WitnessCondition interface and returns condition type. func (c *ConditionCalledByGroup) Type() WitnessConditionType { return WitnessCalledByGroup @@ -440,6 +492,12 @@ func (c *ConditionCalledByGroup) MarshalJSON() ([]byte, error) { return json.Marshal(aux) } +// ToStackItem implements WitnessCondition interface allowing to convert +// to stackitem.Item. +func (c *ConditionCalledByGroup) ToStackItem() stackitem.Item { + return condToStackItem(c.Type(), keys.PublicKey(*c)) +} + // DecodeBinaryCondition decodes and returns condition from the given binary stream. func DecodeBinaryCondition(r *io.BinReader) WitnessCondition { return decodeBinaryCondition(r, MaxConditionNesting) @@ -573,3 +631,29 @@ func unmarshalConditionJSON(data []byte, maxDepth int) (WitnessCondition, error) } return res, nil } + +func condToStackItem(typ WitnessConditionType, c interface{}) stackitem.Item { + res := make([]stackitem.Item, 0, 2) + res = append(res, stackitem.NewBigInteger(big.NewInt(int64(typ)))) + switch typ { + case WitnessBoolean: + res = append(res, stackitem.NewBool(c.(bool))) + case WitnessNot: + res = append(res, c.(WitnessCondition).ToStackItem()) + case WitnessAnd, WitnessOr: + v := c.([]WitnessCondition) + operands := make([]stackitem.Item, len(v)) + for i, op := range v { + operands[i] = op.ToStackItem() + } + res = append(res, stackitem.NewArray(operands)) + case WitnessScriptHash, WitnessCalledByContract: + res = append(res, stackitem.NewByteArray(c.(util.Uint160).BytesBE())) + case WitnessGroup, WitnessCalledByGroup: + g := c.(keys.PublicKey) + res = append(res, stackitem.NewByteArray((&g).Bytes())) + case WitnessCalledByEntry: + // No additional item should be added. + } + return stackitem.NewArray(res) +} diff --git a/pkg/core/transaction/witness_condition_test.go b/pkg/core/transaction/witness_condition_test.go index 22afbd232..e8ac6d4fc 100644 --- a/pkg/core/transaction/witness_condition_test.go +++ b/pkg/core/transaction/witness_condition_test.go @@ -8,6 +8,8 @@ import ( "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" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,10 +32,14 @@ func (c InvalidCondition) MarshalJSON() ([]byte, error) { } return json.Marshal(aux) } +func (c InvalidCondition) ToStackItem() stackitem.Item { + panic("invalid") +} type condCase struct { - condition WitnessCondition - success bool + condition WitnessCondition + success bool + expectedStackItem []stackitem.Item } func TestWitnessConditionSerDes(t *testing.T) { @@ -41,19 +47,25 @@ func TestWitnessConditionSerDes(t *testing.T) { pk, err := keys.NewPrivateKey() require.NoError(t, err) var cases = []condCase{ - {(*ConditionBoolean)(&someBool), true}, - {&ConditionNot{(*ConditionBoolean)(&someBool)}, true}, - {&ConditionAnd{(*ConditionBoolean)(&someBool), (*ConditionBoolean)(&someBool)}, true}, - {&ConditionOr{(*ConditionBoolean)(&someBool), (*ConditionBoolean)(&someBool)}, true}, - {&ConditionScriptHash{1, 2, 3}, true}, - {(*ConditionGroup)(pk.PublicKey()), true}, - {ConditionCalledByEntry{}, true}, - {&ConditionCalledByContract{1, 2, 3}, true}, - {(*ConditionCalledByGroup)(pk.PublicKey()), true}, - {InvalidCondition{}, false}, - {&ConditionAnd{}, false}, - {&ConditionOr{}, false}, - {&ConditionNot{&ConditionNot{&ConditionNot{(*ConditionBoolean)(&someBool)}}}, false}, + {(*ConditionBoolean)(&someBool), true, []stackitem.Item{stackitem.Make(WitnessBoolean), stackitem.Make(someBool)}}, + {&ConditionNot{(*ConditionBoolean)(&someBool)}, true, []stackitem.Item{stackitem.Make(WitnessNot), stackitem.NewArray([]stackitem.Item{stackitem.Make(WitnessBoolean), stackitem.Make(someBool)})}}, + {&ConditionAnd{(*ConditionBoolean)(&someBool), (*ConditionBoolean)(&someBool)}, true, []stackitem.Item{stackitem.Make(WitnessAnd), stackitem.Make([]stackitem.Item{ + stackitem.NewArray([]stackitem.Item{stackitem.Make(WitnessBoolean), stackitem.Make(someBool)}), + stackitem.NewArray([]stackitem.Item{stackitem.Make(WitnessBoolean), stackitem.Make(someBool)}), + })}}, + {&ConditionOr{(*ConditionBoolean)(&someBool), (*ConditionBoolean)(&someBool)}, true, []stackitem.Item{stackitem.Make(WitnessOr), stackitem.Make([]stackitem.Item{ + stackitem.NewArray([]stackitem.Item{stackitem.Make(WitnessBoolean), stackitem.Make(someBool)}), + stackitem.NewArray([]stackitem.Item{stackitem.Make(WitnessBoolean), stackitem.Make(someBool)}), + })}}, + {&ConditionScriptHash{1, 2, 3}, true, []stackitem.Item{stackitem.Make(WitnessScriptHash), stackitem.Make(util.Uint160{1, 2, 3}.BytesBE())}}, + {(*ConditionGroup)(pk.PublicKey()), true, []stackitem.Item{stackitem.Make(WitnessGroup), stackitem.Make(pk.PublicKey().Bytes())}}, + {ConditionCalledByEntry{}, true, []stackitem.Item{stackitem.Make(WitnessCalledByEntry)}}, + {&ConditionCalledByContract{1, 2, 3}, true, []stackitem.Item{stackitem.Make(WitnessCalledByContract), stackitem.Make(util.Uint160{1, 2, 3}.BytesBE())}}, + {(*ConditionCalledByGroup)(pk.PublicKey()), true, []stackitem.Item{stackitem.Make(WitnessCalledByGroup), stackitem.Make(pk.PublicKey().Bytes())}}, + {InvalidCondition{}, false, nil}, + {&ConditionAnd{}, false, nil}, + {&ConditionOr{}, false, nil}, + {&ConditionNot{&ConditionNot{&ConditionNot{(*ConditionBoolean)(&someBool)}}}, false, nil}, } var maxSubCondAnd = &ConditionAnd{} var maxSubCondOr = &ConditionAnd{} @@ -61,8 +73,8 @@ func TestWitnessConditionSerDes(t *testing.T) { *maxSubCondAnd = append(*maxSubCondAnd, (*ConditionBoolean)(&someBool)) *maxSubCondOr = append(*maxSubCondOr, (*ConditionBoolean)(&someBool)) } - cases = append(cases, condCase{maxSubCondAnd, false}) - cases = append(cases, condCase{maxSubCondOr, false}) + cases = append(cases, condCase{maxSubCondAnd, false, nil}) + cases = append(cases, condCase{maxSubCondOr, false, nil}) t.Run("binary", func(t *testing.T) { for i, c := range cases { w := io.NewBufBinWriter() @@ -94,6 +106,15 @@ func TestWitnessConditionSerDes(t *testing.T) { require.Equal(t, c.condition, res) } }) + t.Run("stackitem", func(t *testing.T) { + for i, c := range cases[1:] { + if c.expectedStackItem != nil { + expected := stackitem.NewArray(c.expectedStackItem) + actual := c.condition.ToStackItem() + assert.Equal(t, expected, actual, i) + } + } + }) } func TestWitnessConditionZeroDeser(t *testing.T) { diff --git a/pkg/core/transaction/witness_rule.go b/pkg/core/transaction/witness_rule.go index 75ab4abcd..15edeb699 100644 --- a/pkg/core/transaction/witness_rule.go +++ b/pkg/core/transaction/witness_rule.go @@ -3,8 +3,10 @@ package transaction import ( "encoding/json" "errors" + "math/big" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) //go:generate stringer -type=WitnessAction -linecomment @@ -84,3 +86,11 @@ func (w *WitnessRule) UnmarshalJSON(data []byte) error { w.Condition = cond return nil } + +// ToStackItem implements Convertible interface. +func (w *WitnessRule) ToStackItem() stackitem.Item { + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewBigInteger(big.NewInt(int64(w.Action))), + w.Condition.ToStackItem(), + }) +} diff --git a/pkg/core/transaction/witness_rule_test.go b/pkg/core/transaction/witness_rule_test.go index 70bf9c38d..bde78568a 100644 --- a/pkg/core/transaction/witness_rule_test.go +++ b/pkg/core/transaction/witness_rule_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -54,3 +55,21 @@ func TestWitnessRuleBadJSON(t *testing.T) { require.Errorf(t, err, "case %d, json %s", i, cases[i]) } } + +func TestWitnessRule_ToStackItem(t *testing.T) { + var b bool + for _, act := range []WitnessAction{WitnessDeny, WitnessAllow} { + expected := stackitem.NewArray([]stackitem.Item{ + stackitem.Make(int64(act)), + stackitem.Make([]stackitem.Item{ + stackitem.Make(WitnessBoolean), + stackitem.Make(b), + }), + }) + actual := (&WitnessRule{ + Action: act, + Condition: (*ConditionBoolean)(&b), + }).ToStackItem() + require.Equal(t, expected, actual, act) + } +}