Merge pull request #2702 from nspcc-dev/management-id-hash

Store ID->hash map in the ContractManagement
This commit is contained in:
Roman Khimov 2022-12-02 23:48:44 +07:00 committed by GitHub
commit a654396150
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 509 additions and 130 deletions

View file

@ -27,6 +27,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core"
"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/native"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -856,8 +857,7 @@ func handleLoadDeployed(c *cli.Context) error {
if err != nil {
return fmt.Errorf("failed to parse contract hash, address or ID: %w", err)
}
bc := getChainFromContext(c.App)
h, err = bc.GetContractScriptHash(int32(i)) // @fixme: can be improved after #2702 to retrieve historic state of destroyed contract by ID.
h, err = native.GetContractScriptHash(ic.DAO, int32(i))
if err != nil {
return fmt.Errorf("failed to retrieve contract hash by ID: %w", err)
}

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/engine
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/events
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/iterator
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/nft
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -4,7 +4,7 @@ go 1.17
require (
github.com/nspcc-dev/neo-go v0.99.5-0.20221108145959-8746d9877eb5
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb
github.com/stretchr/testify v1.8.0
)

View file

@ -187,8 +187,8 @@ github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22/go.mod h
github.com/nspcc-dev/hrw v1.0.9 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y=
github.com/nspcc-dev/neo-go v0.99.5-0.20221108145959-8746d9877eb5 h1:NCIUxkLRB3ovLzM1lvQA6wBNn8fuY7dQx4cMJKLuaAs=
github.com/nspcc-dev/neo-go v0.99.5-0.20221108145959-8746d9877eb5/go.mod h1:aWrWJZBYO+9kYC4+qJXvEjySW1WIyPnrHpmdrzd5mJY=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.1 h1:SVqc523pZsSaS9vnPS1mm3VV6b6xY0gvdA0uYJ/GWZQ=
github.com/nspcc-dev/neofs-crypto v0.4.0 h1:5LlrUAM5O0k1+sH/sktBtrgfWtq1pgpDs09fZo+KYi4=
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659 h1:rpMCoRa7expLc9gMiOP724gz6YSykZzmMALR/CmiwnU=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/nft-nd
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/oracle
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/runtime
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/storage
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/timer
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/token
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

2
go.mod
View file

@ -12,7 +12,7 @@ require (
github.com/mr-tron/base58 v1.2.0
github.com/nspcc-dev/dbft v0.0.0-20221020093431-31c1bbdc74f2
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb
github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659
github.com/nspcc-dev/rfc6979 v0.2.0
github.com/pierrec/lz4 v2.6.1+incompatible

4
go.sum
View file

@ -260,8 +260,8 @@ github.com/nspcc-dev/hrw v1.0.9 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y=
github.com/nspcc-dev/hrw v1.0.9/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkPG06MU=
github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg=
github.com/nspcc-dev/neo-go v0.98.0/go.mod h1:E3cc1x6RXSXrJb2nDWXTXjnXk3rIqVN8YdFyWv+FrqM=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.0-pre.0.20211201134523-3604d96f3fe1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.1 h1:SVqc523pZsSaS9vnPS1mm3VV6b6xY0gvdA0uYJ/GWZQ=
github.com/nspcc-dev/neofs-api-go/v2 v2.11.1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=

View file

@ -2,4 +2,4 @@ module github.com/nspcc-dev/neo-go/examples/oracle
go 1.17
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb

View file

@ -1,2 +1,2 @@
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff h1:iCNHkQg9mZEoo+Vr6XwmCUTeWHeumwb8tZV1Y43NKqo=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221125121149-de2de986a7ff/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=

View file

@ -205,6 +205,8 @@ func TestNativeHelpersCompile(t *testing.T) {
{"deployWithData", []string{"nil", "nil", "123"}},
{"destroy", nil},
{"getContract", []string{u160}},
{"getContractById", []string{"1"}},
{"getContractHashes", nil},
{"getMinimumDeploymentFee", nil},
{"hasMethod", []string{u160, `"method"`, "0"}},
{"setMinimumDeploymentFee", []string{"42"}},
@ -300,6 +302,7 @@ func addNativeTestCase(t *testing.T, srcBuilder *bytes.Buffer, ctr interop.Contr
methodUpper := strings.ToUpper(method[:1]) + method[1:] // ASCII only
methodUpper = strings.ReplaceAll(methodUpper, "Gas", "GAS")
methodUpper = strings.ReplaceAll(methodUpper, "Json", "JSON")
methodUpper = strings.ReplaceAll(methodUpper, "Id", "ID")
srcBuilder.WriteString(name)
srcBuilder.WriteRune('.')
srcBuilder.WriteString(methodUpper)

View file

@ -45,7 +45,7 @@ import (
// Tuning parameters.
const (
version = "0.2.6"
version = "0.2.7"
defaultInitialGAS = 52000000_00000000
defaultGCPeriod = 10000
@ -717,21 +717,9 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage)
p = time.Now()
fallthrough
case staleBlocksRemoved:
// Completely remove contract IDs to update them later.
bc.log.Info("trying to reset contract storage items and IDs")
bc.log.Info("trying to reset contract storage items")
pStorageStart := p
cache.Store.Seek(storage.SeekRange{Prefix: []byte{byte(storage.STContractID)}}, func(k, _ []byte) bool {
cache.Store.Delete(k)
return true
})
keys, err = cache.Persist()
if err != nil {
return fmt.Errorf("failed to persist removed contract IDs: %w", err)
}
bc.log.Info("removed contract IDs are persisted", zap.Duration("took", time.Since(p)), zap.Int("keys", keys))
p = time.Now()
// Reset contracts storage and store new contract IDs.
var mode = mpt.ModeAll
if bc.config.RemoveUntraceableBlocks {
mode |= mpt.ModeGCFlag
@ -739,19 +727,12 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage)
trieStore := mpt.NewTrieStore(sr.Root, mode, cache.Store)
oldStoragePrefix := v.StoragePrefix
newStoragePrefix := statesync.TemporaryPrefix(oldStoragePrefix)
mgmtCSPrefixLen := 1 + 4 + 1 // STStorage + Management ID + contract state prefix
mgmtContractPrefix := make([]byte, mgmtCSPrefixLen-1)
id := int32(native.ManagementContractID)
binary.BigEndian.PutUint32(mgmtContractPrefix, uint32(id))
mgmtContractPrefix[4] = native.PrefixContract
cs := new(state.Contract)
const persistBatchSize = 200000
var (
seekErr error
cnt int
storageItmsCnt int
contractIDsCnt int
batchCnt int
)
trieStore.Seek(storage.SeekRange{Prefix: []byte{byte(oldStoragePrefix)}}, func(k, v []byte) bool {
@ -775,22 +756,6 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage)
cnt++
storageItmsCnt++
// @fixme: remove this part after #2702.
if bytes.HasPrefix(k[1:], mgmtContractPrefix) {
var hash util.Uint160
copy(hash[:], k[mgmtCSPrefixLen:])
err = stackitem.DeserializeConvertible(v, cs)
if err != nil {
bc.log.Warn("failed to deserialize contract; ID for this contract won't be stored in the DB",
zap.String("hash", hash.StringLE()),
zap.Error(err))
} else {
cache.PutContractID(cs.ID, hash)
cnt++
contractIDsCnt++
}
}
return true
})
if seekErr != nil {
@ -806,8 +771,7 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage)
batchCnt++
bc.log.Info("last batch of contract storage items and IDs is persisted", zap.Int("batch", batchCnt), zap.Duration("took", time.Since(p)), zap.Int("keys", keys))
bc.log.Info("contract storage items and IDs are reset", zap.Duration("took", time.Since(pStorageStart)),
zap.Int("keys", storageItmsCnt),
zap.Int("ids", contractIDsCnt))
zap.Int("keys", storageItmsCnt))
p = time.Now()
fallthrough
case newStorageItemsAdded:
@ -1774,7 +1738,7 @@ func (bc *Blockchain) processTokenTransfer(cache *dao.Simple, transCache map[uti
if nativeContract != nil {
id = nativeContract.Metadata().ID
} else {
assetContract, err := bc.contracts.Management.GetContract(cache, sc)
assetContract, err := native.GetContract(cache, sc)
if err != nil {
return
}
@ -2112,7 +2076,7 @@ func (bc *Blockchain) BlockHeight() uint32 {
// GetContractState returns contract by its script hash.
func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract {
contract, err := bc.contracts.Management.GetContract(bc.dao, hash)
contract, err := native.GetContract(bc.dao, hash)
if contract == nil && !errors.Is(err, storage.ErrKeyNotFound) {
bc.log.Warn("failed to get contract state", zap.Error(err))
}
@ -2121,7 +2085,7 @@ func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract {
// GetContractScriptHash returns contract script hash by its ID.
func (bc *Blockchain) GetContractScriptHash(id int32) (util.Uint160, error) {
return bc.dao.GetContractScriptHash(id)
return native.GetContractScriptHash(bc.dao, id)
}
// GetNativeContractScriptHash returns native contract script hash by its name.
@ -2823,7 +2787,7 @@ func (bc *Blockchain) newInteropContext(trigger trigger.Type, d *dao.Simple, blo
// changes that were not yet persisted to Blockchain's dao.
baseStorageFee = bc.contracts.Policy.GetStoragePriceInternal(d)
}
ic := interop.NewContext(trigger, bc, d, baseExecFee, baseStorageFee, bc.contracts.Management.GetContract, bc.contracts.Contracts, contract.LoadToken, block, tx, bc.log)
ic := interop.NewContext(trigger, bc, d, baseExecFee, baseStorageFee, native.GetContract, bc.contracts.Contracts, contract.LoadToken, block, tx, bc.log)
ic.Functions = systemInterops
switch {
case tx != nil:

View file

@ -135,32 +135,6 @@ func (dao *Simple) putWithBuffer(entity io.Serializable, key []byte, buf *io.Buf
return nil
}
func (dao *Simple) makeContractIDKey(id int32) []byte {
key := dao.getKeyBuf(5)
key[0] = byte(storage.STContractID)
binary.BigEndian.PutUint32(key[1:], uint32(id))
return key
}
// DeleteContractID deletes contract's id to hash mapping.
func (dao *Simple) DeleteContractID(id int32) {
dao.Store.Delete(dao.makeContractIDKey(id))
}
// PutContractID adds a mapping from a contract's ID to its hash.
func (dao *Simple) PutContractID(id int32, hash util.Uint160) {
dao.Store.Put(dao.makeContractIDKey(id), hash.BytesBE())
}
// GetContractScriptHash retrieves the contract's hash given its ID.
func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) {
var data = new(util.Uint160)
if err := dao.GetAndDecode(data, dao.makeContractIDKey(id)); err != nil {
return *data, err
}
return *data, nil
}
// -- start NEP-17 transfer info.
func (dao *Simple) makeTTIKey(acc util.Uint160) []byte {

View file

@ -1,6 +1,8 @@
package native
import (
"context"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
@ -11,6 +13,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
@ -45,6 +48,7 @@ const (
// PrefixContract is a prefix used to store contract states inside Management native contract.
PrefixContract = 8
prefixContractHash = 12
defaultMinimumDeploymentFee = 10_00000000
contractDeployNotificationName = "Deploy"
@ -149,6 +153,15 @@ func newManagement() *Management {
md = newMethodAndPrice(m.hasMethod, 1<<15, callflag.ReadStates)
m.AddMethod(md, desc)
desc = newDescriptor("getContractById", smartcontract.ArrayType,
manifest.NewParameter("id", smartcontract.IntegerType))
md = newMethodAndPrice(m.getContractByID, 1<<15, callflag.ReadStates)
m.AddMethod(md, desc)
desc = newDescriptor("getContractHashes", smartcontract.InteropInterfaceType)
md = newMethodAndPrice(m.getContractHashes, 1<<15, callflag.ReadStates)
m.AddMethod(md, desc)
hashParam := manifest.NewParameter("Hash", smartcontract.Hash160Type)
m.AddEvent(contractDeployNotificationName, hashParam)
m.AddEvent(contractUpdateNotificationName, hashParam)
@ -172,7 +185,28 @@ func toHash160(si stackitem.Item) util.Uint160 {
// VM protections, so it's OK for it to panic instead of returning errors.
func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) stackitem.Item {
hash := toHash160(args[0])
ctr, err := m.GetContract(ic.DAO, hash)
ctr, err := GetContract(ic.DAO, hash)
if err != nil {
if errors.Is(err, storage.ErrKeyNotFound) {
return stackitem.Null{}
}
panic(err)
}
return contractToStack(ctr)
}
// getContractByID is an implementation of public getContractById method, it's run under
// VM protections, so it's OK for it to panic instead of returning errors.
func (m *Management) getContractByID(ic *interop.Context, args []stackitem.Item) stackitem.Item {
idBig, err := args[0].TryInteger()
if err != nil {
panic(err)
}
id := idBig.Int64()
if !idBig.IsInt64() || id < math.MinInt32 || id > math.MaxInt32 {
panic("id is not a correct int32")
}
ctr, err := GetContractByID(ic.DAO, int32(id))
if err != nil {
if errors.Is(err, storage.ErrKeyNotFound) {
return stackitem.Null{}
@ -183,8 +217,8 @@ func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) sta
}
// GetContract returns a contract with the given hash from the given DAO.
func (m *Management) GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) {
cache := d.GetROCache(m.ID).(*ManagementCache)
func GetContract(d *dao.Simple, hash util.Uint160) (*state.Contract, error) {
cache := d.GetROCache(ManagementContractID).(*ManagementCache)
cs, ok := cache.contracts[hash]
if !ok {
return nil, storage.ErrKeyNotFound
@ -192,6 +226,26 @@ func (m *Management) GetContract(d *dao.Simple, hash util.Uint160) (*state.Contr
return cs, nil
}
// GetContractByID returns a contract with the given ID from the given DAO.
func GetContractByID(d *dao.Simple, id int32) (*state.Contract, error) {
hash, err := GetContractScriptHash(d, id)
if err != nil {
return nil, err
}
return GetContract(d, hash)
}
// GetContractScriptHash returns a contract hash associated with the given ID from the given DAO.
func GetContractScriptHash(d *dao.Simple, id int32) (util.Uint160, error) {
key := make([]byte, 5)
key = putHashKey(key, id)
si := d.GetStorageItem(ManagementContractID, key)
if si == nil {
return util.Uint160{}, storage.ErrKeyNotFound
}
return util.Uint160DecodeBytesBE(si)
}
func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) {
_, isNull := arg.(stackitem.Null)
if isNull {
@ -211,6 +265,29 @@ func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) {
return b, nil
}
func (m *Management) getContractHashes(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
ctx, cancel := context.WithCancel(context.Background())
prefix := []byte{prefixContractHash}
seekres := ic.DAO.SeekAsync(ctx, ManagementContractID, storage.SeekRange{Prefix: prefix})
filteredRes := make(chan storage.KeyValue)
go func() {
for kv := range seekres {
if len(kv.Key) == 4 && binary.BigEndian.Uint32(kv.Key) < math.MaxInt32 {
filteredRes <- kv
}
}
close(filteredRes)
}()
opts := istorage.FindRemovePrefix
item := istorage.NewIterator(filteredRes, prefix, int64(opts))
ic.RegisterCancelFunc(func() {
cancel()
for range seekres {
}
})
return stackitem.NewInterop(item)
}
// getNefAndManifestFromItems converts input arguments into NEF and manifest
// adding an appropriate deployment GAS price and sanitizing inputs.
func (m *Management) getNefAndManifestFromItems(ic *interop.Context, args []stackitem.Item, isDeploy bool) (*nef.File, *manifest.Manifest, error) {
@ -303,7 +380,7 @@ func (m *Management) Deploy(d *dao.Simple, sender util.Uint160, neff *nef.File,
if m.Policy.IsBlocked(d, h) {
return nil, fmt.Errorf("the contract %s has been blocked", h.StringLE())
}
_, err := m.GetContract(d, h)
_, err := GetContract(d, h)
if err == nil {
return nil, errors.New("contract already exists")
}
@ -362,7 +439,7 @@ func (m *Management) updateWithData(ic *interop.Context, args []stackitem.Item)
func (m *Management) Update(d *dao.Simple, hash util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) {
var contract state.Contract
oldcontract, err := m.GetContract(d, hash)
oldcontract, err := GetContract(d, hash)
if err != nil {
return nil, errors.New("contract doesn't exist")
}
@ -412,13 +489,14 @@ func (m *Management) destroy(ic *interop.Context, sis []stackitem.Item) stackite
// Destroy drops the given contract from DAO along with its storage. It doesn't emit notification.
func (m *Management) Destroy(d *dao.Simple, hash util.Uint160) error {
contract, err := m.GetContract(d, hash)
contract, err := GetContract(d, hash)
if err != nil {
return err
}
key := MakeContractKey(hash)
d.DeleteStorageItem(m.ID, key)
d.DeleteContractID(contract.ID)
key = putHashKey(key, contract.ID)
d.DeleteStorageItem(ManagementContractID, key)
d.Seek(contract.ID, storage.SeekRange{}, func(k, _ []byte) bool {
d.DeleteStorageItem(contract.ID, k)
@ -476,7 +554,7 @@ func (m *Management) hasMethod(ic *interop.Context, args []stackitem.Item) stack
panic(err)
}
pcount := int(toInt64((args[2])))
cs, err := m.GetContract(ic.DAO, cHash)
cs, err := GetContract(ic.DAO, cHash)
if err != nil {
return stackitem.NewBool(false)
}
@ -610,10 +688,17 @@ func putContractState(d *dao.Simple, cs *state.Contract, updateCache bool) error
if cs.UpdateCounter != 0 { // Update.
return nil
}
d.PutContractID(cs.ID, cs.Hash)
key = putHashKey(key, cs.ID)
d.PutStorageItem(ManagementContractID, key, cs.Hash.BytesBE())
return nil
}
func putHashKey(buf []byte, id int32) []byte {
buf[0] = prefixContractHash
binary.BigEndian.PutUint32(buf[1:], uint32(id))
return buf[:5]
}
func (m *Management) getNextContractID(d *dao.Simple) (int32, error) {
si := d.GetStorageItem(m.ID, keyNextAvailableID)
if si == nil {

View file

@ -57,7 +57,11 @@ func TestDeployGetUpdateDestroyContract(t *testing.T) {
require.Equal(t, ne, &contract2.NEF)
require.Equal(t, *manif, contract2.Manifest)
refContract, err := mgmt.GetContract(d, h)
refContract, err := GetContract(d, h)
require.NoError(t, err)
require.Equal(t, contract, refContract)
refContract, err = GetContractByID(d, contract.ID)
require.NoError(t, err)
require.Equal(t, contract, refContract)
@ -68,7 +72,9 @@ func TestDeployGetUpdateDestroyContract(t *testing.T) {
err = mgmt.Destroy(d, h)
require.NoError(t, err)
_, err = mgmt.GetContract(d, h)
_, err = GetContract(d, h)
require.Error(t, err)
_, err = GetContractByID(d, contract.ID)
require.Error(t, err)
}
@ -140,11 +146,11 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
// No changes expected in lower store.
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(d))
c1Lower, err := mgmt.GetContract(d, c1.Hash)
c1Lower, err := GetContract(d, c1.Hash)
require.NoError(t, err)
require.Equal(t, 1, len(c1Lower.Manifest.ABI.Methods))
require.Equal(t, []util.Uint160{c1Updated.Hash}, mgmt.GetNEP17Contracts(private))
c1Upper, err := mgmt.GetContract(private, c1Updated.Hash)
c1Upper, err := GetContract(private, c1Updated.Hash)
require.NoError(t, err)
require.Equal(t, 2, len(c1Upper.Manifest.ABI.Methods))
@ -152,7 +158,7 @@ func TestManagement_GetNEP17Contracts(t *testing.T) {
_, err = private.Persist()
require.NoError(t, err)
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts(d))
c1Lower, err = mgmt.GetContract(d, c1.Hash)
c1Lower, err = GetContract(d, c1.Hash)
require.NoError(t, err)
require.Equal(t, 2, len(c1Lower.Manifest.ABI.Methods))
}

View file

@ -8,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
@ -556,6 +557,41 @@ func TestManagement_GetContract(t *testing.T) {
t.Run("positive", func(t *testing.T) {
managementInvoker.Invoke(t, si, "getContract", cs1.Hash.BytesBE())
})
t.Run("by ID, bad parameter type", func(t *testing.T) {
managementInvoker.InvokeFail(t, "invalid conversion: Array/Integer", "getContractById", []interface{}{int64(1)})
})
t.Run("by ID, bad num", func(t *testing.T) {
managementInvoker.InvokeFail(t, "id is not a correct int32", "getContractById", []byte{1, 2, 3, 4, 5})
})
t.Run("by ID, positive", func(t *testing.T) {
managementInvoker.Invoke(t, si, "getContractById", cs1.ID)
})
t.Run("by ID, native", func(t *testing.T) {
csm := managementInvoker.Executor.Chain.GetContractState(managementInvoker.Hash)
require.NotNil(t, csm)
sim, err := csm.ToStackItem()
require.NoError(t, err)
managementInvoker.Invoke(t, sim, "getContractById", -1)
})
t.Run("by ID, empty", func(t *testing.T) {
managementInvoker.Invoke(t, stackitem.Null{}, "getContractById", -100)
})
t.Run("contract hashes", func(t *testing.T) {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, managementInvoker.Hash, "getContractHashes", callflag.All)
emit.Opcodes(w.BinWriter, opcode.DUP) // Iterator.
emit.Syscall(w.BinWriter, interopnames.SystemIteratorNext)
emit.Opcodes(w.BinWriter, opcode.ASSERT) // Has one element.
emit.Opcodes(w.BinWriter, opcode.DUP) // Iterator.
emit.Syscall(w.BinWriter, interopnames.SystemIteratorValue)
emit.Opcodes(w.BinWriter, opcode.SWAP) // Iterator to the top.
emit.Syscall(w.BinWriter, interopnames.SystemIteratorNext)
emit.Opcodes(w.BinWriter, opcode.NOT)
emit.Opcodes(w.BinWriter, opcode.ASSERT) // No more elements, single value left on the stack.
require.NoError(t, w.Err)
h := managementInvoker.InvokeScript(t, w.Bytes(), managementInvoker.Signers)
managementInvoker.Executor.CheckHalt(t, h, stackitem.NewStruct([]stackitem.Item{stackitem.Make([]byte{0, 0, 0, 1}), stackitem.Make(cs1.Hash.BytesBE())}))
})
}
func TestManagement_ContractDestroy(t *testing.T) {

View file

@ -17,7 +17,6 @@ const (
// DataMPTAux is used to store additional MPT data like height-root
// mappings and local/validated heights.
DataMPTAux KeyPrefix = 0x04
STContractID KeyPrefix = 0x51
STStorage KeyPrefix = 0x70
// STTempStorage is used to store contract storage items during state sync process
// in order not to mess up the previous state which has its own items stored by

View file

@ -7,12 +7,21 @@ package management
import (
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
)
// Hash represents Management contract hash.
const Hash = "\xfd\xa3\xfa\x43\x46\xea\x53\x2a\x25\x8f\xc4\x97\xdd\xad\xdb\x64\x37\xc9\xfd\xff"
// IDHash is an ID/Hash pair returned by the iterator from the GetContractHashes method.
type IDHash struct {
// ID is a 32-bit number, but it's represented in big endian form
// natively, because that's the key scheme used by ContractManagement.
ID []byte
Hash interop.Hash160
}
// Deploy represents `deploy` method of Management native contract.
func Deploy(script, manifest []byte) *Contract {
return neogointernal.CallWithToken(Hash, "deploy",
@ -35,6 +44,19 @@ func GetContract(addr interop.Hash160) *Contract {
return neogointernal.CallWithToken(Hash, "getContract", int(contract.ReadStates), addr).(*Contract)
}
// GetContractByID represents `getContractById` method of the Management native contract.
func GetContractByID(id int) *Contract {
return neogointernal.CallWithToken(Hash, "getContractById", int(contract.ReadStates), id).(*Contract)
}
// GetContractHashes represents `getContractHashes` method of the Management
// native contract. It returns an Iterator over the list of non-native contract
// hashes. Each iterator value can be cast to IDHash. Use [iterator] interop
// package to work with the returned Iterator.
func GetContractHashes() iterator.Iterator {
return neogointernal.CallWithToken(Hash, "getContractHashes", int(contract.ReadStates)).(iterator.Iterator)
}
// GetMinimumDeploymentFee represents `getMinimumDeploymentFee` method of Management native contract.
func GetMinimumDeploymentFee() int {
return neogointernal.CallWithToken(Hash, "getMinimumDeploymentFee", int(contract.ReadStates)).(int)

View file

@ -7,10 +7,12 @@ various methods to perform state-changing calls.
package management
import (
"encoding/binary"
"encoding/json"
"fmt"
"math/big"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -20,11 +22,15 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Invoker is used by ContractReader to call various methods.
type Invoker interface {
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error)
TerminateSession(sessionID uuid.UUID) error
TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
}
// Actor is used by Contract to create and send transactions.
@ -55,6 +61,19 @@ type Contract struct {
actor Actor
}
// IDHash is an ID/Hash pair returned by the iterator from the GetContractHashes method.
type IDHash struct {
ID int32
Hash util.Uint160
}
// HashesIterator is used for iterating over GetContractHashes results.
type HashesIterator struct {
client Invoker
session uuid.UUID
iterator result.Iterator
}
// Hash stores the hash of the native ContractManagement contract.
var Hash = state.CreateNativeContractHash(nativenames.Management)
@ -83,7 +102,16 @@ func New(actor Actor) *Contract {
// getcontractstate RPC API that has more options and works faster than going
// via contract invocation.
func (c *ContractReader) GetContract(hash util.Uint160) (*state.Contract, error) {
itm, err := unwrap.Item(c.invoker.Call(Hash, "getContract", hash))
return unwrapContract(c.invoker.Call(Hash, "getContract", hash))
}
// GetContractByID allows to get contract data from its ID.
func (c *ContractReader) GetContractByID(id int32) (*state.Contract, error) {
return unwrapContract(c.invoker.Call(Hash, "getContractById", id))
}
func unwrapContract(r *result.Invoke, err error) (*state.Contract, error) {
itm, err := unwrap.Item(r, err)
if err != nil {
return nil, err
}
@ -95,6 +123,88 @@ func (c *ContractReader) GetContract(hash util.Uint160) (*state.Contract, error)
return res, nil
}
// GetContractHashes returns an iterator that allows to retrieve all ID-hash
// mappings for non-native contracts. It depends on the server to provide proper
// session-based iterator, but can also work with expanded one.
func (c *ContractReader) GetContractHashes() (*HashesIterator, error) {
sess, iter, err := unwrap.SessionIterator(c.invoker.Call(Hash, "getContractHashes"))
if err != nil {
return nil, err
}
return &HashesIterator{
client: c.invoker,
iterator: iter,
session: sess,
}, nil
}
// GetContractHashesExpanded is similar to GetContractHashes (uses the same
// contract method), but can be useful if the server used doesn't support
// sessions and doesn't expand iterators. It creates a script that will get num
// of result items from the iterator right in the VM and return them to you. It's
// only limited by VM stack and GAS available for RPC invocations.
func (c *ContractReader) GetContractHashesExpanded(num int) ([]IDHash, error) {
arr, err := unwrap.Array(c.invoker.CallAndExpandIterator(Hash, "getContractHashes", num))
if err != nil {
return nil, err
}
return itemsToIDHashes(arr)
}
// Next returns the next set of elements from the iterator (up to num of them).
// It can return less than num elements in case iterator doesn't have that many
// or zero elements if the iterator has no more elements or the session is
// expired.
func (h *HashesIterator) Next(num int) ([]IDHash, error) {
items, err := h.client.TraverseIterator(h.session, &h.iterator, num)
if err != nil {
return nil, err
}
return itemsToIDHashes(items)
}
// Terminate closes the iterator session used by HashesIterator (if it's
// session-based).
func (h *HashesIterator) Terminate() error {
if h.iterator.ID == nil {
return nil
}
return h.client.TerminateSession(h.session)
}
func itemsToIDHashes(arr []stackitem.Item) ([]IDHash, error) {
res := make([]IDHash, len(arr))
for i, itm := range arr {
str, ok := itm.Value().([]stackitem.Item)
if !ok {
return nil, fmt.Errorf("item #%d is not a structure %T", i, itm.Value())
}
if len(str) != 2 {
return nil, fmt.Errorf("item #%d has wrong length", i)
}
bi, err := str[0].TryBytes()
if err != nil {
return nil, fmt.Errorf("item #%d has wrong ID: %w", i, err)
}
if len(bi) != 4 {
return nil, fmt.Errorf("item #%d has wrong ID: bad length", i)
}
id := int32(binary.BigEndian.Uint32(bi))
hb, err := str[1].TryBytes()
if err != nil {
return nil, fmt.Errorf("item #%d has wrong hash: %w", i, err)
}
u160, err := util.Uint160DecodeBytesBE(hb)
if err != nil {
return nil, fmt.Errorf("item #%d has wrong hash: %w", i, err)
}
res[i].ID = id
res[i].Hash = u160
}
return res, nil
}
// GetMinimumDeploymentFee returns the minimal amount of GAS needed to deploy a
// contract on the network.
func (c *ContractReader) GetMinimumDeploymentFee() (*big.Int, error) {

View file

@ -5,6 +5,7 @@ import (
"math/big"
"testing"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
@ -43,6 +44,15 @@ func (t *testAct) MakeUnsignedRun(script []byte, attrs []transaction.Attribute)
func (t *testAct) SendRun(script []byte) (util.Uint256, uint32, error) {
return t.txh, t.vub, t.err
}
func (t *testAct) CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error) {
return t.res, t.err
}
func (t *testAct) TerminateSession(sessionID uuid.UUID) error {
return t.err
}
func (t *testAct) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) {
return t.res.Stack, t.err
}
func TestReader(t *testing.T) {
ta := new(testAct)
@ -51,6 +61,8 @@ func TestReader(t *testing.T) {
ta.err = errors.New("")
_, err := man.GetContract(util.Uint160{1, 2, 3})
require.Error(t, err)
_, err = man.GetContractByID(1)
require.Error(t, err)
_, err = man.GetMinimumDeploymentFee()
require.Error(t, err)
_, err = man.HasMethod(util.Uint160{1, 2, 3}, "method", 0)
@ -65,6 +77,8 @@ func TestReader(t *testing.T) {
}
_, err = man.GetContract(util.Uint160{1, 2, 3})
require.Error(t, err)
_, err = man.GetContractByID(1)
require.Error(t, err)
fee, err := man.GetMinimumDeploymentFee()
require.NoError(t, err)
require.Equal(t, big.NewInt(42), fee)
@ -80,6 +94,8 @@ func TestReader(t *testing.T) {
}
_, err = man.GetContract(util.Uint160{1, 2, 3})
require.Error(t, err)
_, err = man.GetContractByID(1)
require.Error(t, err)
hm, err = man.HasMethod(util.Uint160{1, 2, 3}, "method", 0)
require.NoError(t, err)
require.False(t, hm)
@ -92,6 +108,8 @@ func TestReader(t *testing.T) {
}
_, err = man.GetContract(util.Uint160{1, 2, 3})
require.Error(t, err)
_, err = man.GetContractByID(1)
require.Error(t, err)
nefFile, _ := nef.NewFile([]byte{1, 2, 3})
nefBytes, _ := nefFile.Bytes()
@ -114,6 +132,109 @@ func TestReader(t *testing.T) {
require.Equal(t, int32(1), cs.ID)
require.Equal(t, uint16(0), cs.UpdateCounter)
require.Equal(t, util.Uint160{1, 2, 3}, cs.Hash)
cs2, err := man.GetContractByID(1)
require.NoError(t, err)
require.Equal(t, cs, cs2)
}
func TestGetContractHashes(t *testing.T) {
ta := &testAct{}
man := NewReader(ta)
ta.err = errors.New("")
_, err := man.GetContractHashes()
require.Error(t, err)
_, err = man.GetContractHashesExpanded(5)
require.Error(t, err)
ta.err = nil
iid := uuid.New()
ta.res = &result.Invoke{
State: "HALT",
Stack: []stackitem.Item{
stackitem.NewInterop(result.Iterator{
ID: &iid,
}),
},
}
_, err = man.GetContractHashes()
require.Error(t, err)
// Session-based iterator.
sid := uuid.New()
ta.res = &result.Invoke{
Session: sid,
State: "HALT",
Stack: []stackitem.Item{
stackitem.NewInterop(result.Iterator{
ID: &iid,
}),
},
}
iter, err := man.GetContractHashes()
require.NoError(t, err)
ta.res = &result.Invoke{
Stack: []stackitem.Item{
stackitem.Make([]stackitem.Item{
stackitem.Make([]byte{0, 0, 0, 1}),
stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()),
}),
},
}
vals, err := iter.Next(10)
require.NoError(t, err)
require.Equal(t, 1, len(vals))
require.Equal(t, IDHash{
ID: 1,
Hash: util.Uint160{1, 2, 3},
}, vals[0])
ta.err = errors.New("")
_, err = iter.Next(1)
require.Error(t, err)
err = iter.Terminate()
require.Error(t, err)
// Value-based iterator.
ta.err = nil
ta.res = &result.Invoke{
State: "HALT",
Stack: []stackitem.Item{
stackitem.NewInterop(result.Iterator{
Values: []stackitem.Item{stackitem.NewStruct([]stackitem.Item{
stackitem.Make([]byte{0, 0, 0, 1}),
stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()),
})},
}),
},
}
iter, err = man.GetContractHashes()
require.NoError(t, err)
ta.err = errors.New("")
err = iter.Terminate()
require.NoError(t, err)
// Expanded
ta.err = nil
ta.res = &result.Invoke{
State: "HALT",
Stack: []stackitem.Item{
stackitem.Make([]stackitem.Item{stackitem.Make([]stackitem.Item{
stackitem.Make([]byte{0, 0, 0, 1}),
stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()),
})}),
},
}
vals, err = man.GetContractHashesExpanded(5)
require.NoError(t, err)
require.Equal(t, 1, len(vals))
require.Equal(t, IDHash{
ID: 1,
Hash: util.Uint160{1, 2, 3},
}, vals[0])
}
func TestSetMinimumDeploymentFee(t *testing.T) {
@ -204,3 +325,31 @@ func TestDeploy(t *testing.T) {
// Unfortunately, manifest _always_ marshals successfully (or panics).
}
func TestItemsToIDHashesErrors(t *testing.T) {
for name, input := range map[string][]stackitem.Item{
"not a struct": {stackitem.Make(1)},
"wrong length": {stackitem.Make([]stackitem.Item{})},
"wrong id": {stackitem.Make([]stackitem.Item{
stackitem.Make([]stackitem.Item{}),
stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()),
})},
"lengthy id": {stackitem.Make([]stackitem.Item{
stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()),
stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()),
})},
"not a good hash": {stackitem.Make([]stackitem.Item{
stackitem.Make([]byte{0, 0, 0, 1}),
stackitem.Make([]stackitem.Item{}),
})},
"not a good u160 hash": {stackitem.Make([]stackitem.Item{
stackitem.Make([]byte{0, 0, 0, 1}),
stackitem.Make(util.Uint256{1, 2, 3}.BytesBE()),
})},
} {
t.Run(name, func(t *testing.T) {
_, err := itemsToIDHashes(input)
require.Error(t, err)
})
}
}

View file

@ -256,6 +256,9 @@ func TestClientManagementContract(t *testing.T) {
cs2, err := c.GetContractStateByHash(gas.Hash)
require.NoError(t, err)
require.Equal(t, cs2, cs1)
cs1, err = manReader.GetContractByID(-6)
require.NoError(t, err)
require.Equal(t, cs2, cs1)
ret, err := manReader.HasMethod(gas.Hash, "transfer", 4)
require.NoError(t, err)
@ -275,6 +278,25 @@ func TestClientManagementContract(t *testing.T) {
}})
require.NoError(t, err)
ids, err := manReader.GetContractHashesExpanded(10)
require.NoError(t, err)
ctrs := make([]management.IDHash, 0)
for i, s := range []string{testContractHash, verifyContractHash, verifyWithArgsContractHash, nnsContractHash, nfsoContractHash, storageContractHash} {
h, err := util.Uint160DecodeStringLE(s)
require.NoError(t, err)
ctrs = append(ctrs, management.IDHash{ID: int32(i) + 1, Hash: h})
}
require.Equal(t, ctrs, ids)
iter, err := manReader.GetContractHashes()
require.NoError(t, err)
ids, err = iter.Next(3)
require.NoError(t, err)
require.Equal(t, ctrs[:3], ids)
ids, err = iter.Next(10)
require.NoError(t, err)
require.Equal(t, ctrs[3:], ids)
man := management.New(act)
txfee, err := man.SetMinimumDeploymentFeeUnsigned(big.NewInt(1 * 1_0000_0000))

View file

@ -82,7 +82,7 @@ const (
faultedTxHashLE = "82279bfe9bada282ca0f8cb8e0bb124b921af36f00c69a518320322c6f4fef60"
faultedTxBlock uint32 = 23
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
block20StateRootLE = "b49a045246bf3bb90248ed538dd21e67d782a9242c52f31dfdef3da65ecd87c1"
block20StateRootLE = "13620fef0fb28060523a0b73ce574ee4658fca5d0d24078a73e74a349c37a854"
)
var (
@ -2069,6 +2069,15 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
}
t.Run("ByHeight", func(t *testing.T) { testRoot(t, strconv.FormatInt(5, 10)) })
t.Run("ByHash", func(t *testing.T) { testRoot(t, `"`+chain.GetHeaderHash(5).StringLE()+`"`) })
t.Run("20", func(t *testing.T) {
rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getstateroot", "params": [20]}`
body := doRPCCall(rpc, httpSrv.URL, t)
rawRes := checkErrGetResult(t, body, false)
res := &state.MPTRoot{}
require.NoError(t, json.Unmarshal(rawRes, res))
require.Equal(t, block20StateRootLE, res.Root.StringLE())
})
})
t.Run("getstate", func(t *testing.T) {
testGetState := func(t *testing.T, p string, expected string) {