state: use checksums and names to calculate contract hashes

It allows to deploy the same NEF using one sender and get different contract
hashes. See neo-project/neo#2240.
This commit is contained in:
Roman Khimov 2021-01-22 12:22:48 +03:00
parent 6b9b37f170
commit 054ca27e9c
14 changed files with 76 additions and 39 deletions

View file

@ -17,6 +17,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"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"
@ -33,28 +34,37 @@ func TestCalcHash(t *testing.T) {
require.NoError(t, err)
nefF, err := nef.FileFromBytes(src)
require.NoError(t, err)
manifestPath := "./testdata/verify.manifest.json"
manifestBytes, err := ioutil.ReadFile(manifestPath)
require.NoError(t, err)
manif := &manifest.Manifest{}
err = json.Unmarshal(manifestBytes, manif)
require.NoError(t, err)
sender := random.Uint160()
cmd := []string{"neo-go", "contract", "calc-hash"}
t.Run("no sender", func(t *testing.T) {
e.RunWithError(t, append(cmd, "--in", nefPath)...)
e.RunWithError(t, append(cmd, "--in", nefPath, "--manifest", manifestPath)...)
})
t.Run("no nef file", func(t *testing.T) {
e.RunWithError(t, append(cmd, "--sender", sender.StringLE())...)
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--manifest", manifestPath)...)
})
t.Run("no manifest file", func(t *testing.T) {
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", nefPath)...)
})
t.Run("invalid path", func(t *testing.T) {
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(),
"--in", "./testdata/verify.nef123")...)
"--in", "./testdata/verify.nef123", "--manifest", manifestPath)...)
})
t.Run("invalid file", func(t *testing.T) {
p := path.Join(os.TempDir(), "neogo.calchash.verify.nef")
defer os.Remove(p)
require.NoError(t, ioutil.WriteFile(p, src[:4], os.ModePerm))
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", p)...)
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", p, "--manifest", manifestPath)...)
})
cmd = append(cmd, "--in", nefPath)
expected := state.CreateContractHash(sender, nefF.Script)
cmd = append(cmd, "--in", nefPath, "--manifest", manifestPath)
expected := state.CreateContractHash(sender, nefF.Checksum, manif.Name)
t.Run("valid, uint160", func(t *testing.T) {
e.Run(t, append(cmd, "--sender", sender.StringLE())...)
e.checkNextLine(t, expected.StringLE())
@ -193,7 +203,8 @@ func TestComlileAndInvokeFunction(t *testing.T) {
t.Run("check calc hash", func(t *testing.T) {
e.Run(t, "neo-go", "contract", "calc-hash",
"--sender", validatorAddr, "--in", nefName)
"--sender", validatorAddr, "--in", nefName,
"--manifest", manifestName)
e.checkNextLine(t, h.StringLE())
})

View file

@ -351,6 +351,10 @@ func NewCommands() []cli.Command {
Name: "in",
Usage: "path to NEF file",
},
cli.StringFlag{
Name: "manifest, m",
Usage: "path to manifest file",
},
},
},
},
@ -473,6 +477,10 @@ func calcHash(ctx *cli.Context) error {
if p == "" {
return cli.NewExitError(errors.New("no .nef file was provided"), 1)
}
mpath := ctx.String("manifest")
if mpath == "" {
return cli.NewExitError(errors.New("no manifest file provided"), 1)
}
f, err := ioutil.ReadFile(p)
if err != nil {
return cli.NewExitError(fmt.Errorf("can't read .nef file: %w", err), 1)
@ -481,7 +489,16 @@ func calcHash(ctx *cli.Context) error {
if err != nil {
return cli.NewExitError(fmt.Errorf("can't unmarshal .nef file: %w", err), 1)
}
fmt.Fprintln(ctx.App.Writer, "Contract hash:", state.CreateContractHash(u, nefFile.Script).StringLE())
manifestBytes, err := ioutil.ReadFile(mpath)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to read manifest file: %w", err), 1)
}
m := &manifest.Manifest{}
err = json.Unmarshal(manifestBytes, m)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to restore manifest file: %w", err), 1)
}
fmt.Fprintln(ctx.App.Writer, "Contract hash:", state.CreateContractHash(u, nefFile.Checksum, m.Name).StringLE())
return nil
}
@ -842,7 +859,7 @@ func contractDeploy(ctx *cli.Context) error {
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1)
}
hash := state.CreateContractHash(sender, nefFile.Script)
hash := state.CreateContractHash(sender, nefFile.Checksum, m.Name)
fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", hash.StringLE())
fmt.Fprintln(ctx.App.Writer, txHash.StringLE())
return nil

Binary file not shown.

View file

@ -84,7 +84,7 @@ func NewDeployTx(bc blockchainer.Blockchainer, name string, sender util.Uint160,
tx := transaction.New(Network(), buf.Bytes(), 100*native.GASFactor)
tx.Signers = []transaction.Signer{{Account: sender}}
h := state.CreateContractHash(tx.Sender(), avm)
h := state.CreateContractHash(tx.Sender(), ne.Checksum, name)
return tx, h, nil
}

View file

@ -556,11 +556,11 @@ func TestVerifyTx(t *testing.T) {
})
tx.Scripts = append(tx.Scripts, transaction.Witness{})
t.Run("NonZeroVerification", func(t *testing.T) {
script, _ := state.CreateNativeContractHash(-6) //oracleContractID
w := io.NewBufBinWriter()
emit.Opcodes(w.BinWriter, opcode.ABORT)
emit.Bytes(w.BinWriter, util.Uint160{}.BytesBE())
emit.Bytes(w.BinWriter, script)
emit.Int(w.BinWriter, int64(orc.NEF.Checksum))
emit.String(w.BinWriter, orc.Manifest.Name)
tx.Scripts[len(tx.Scripts)-1].VerificationScript = w.Bytes()
err := bc.VerifyTx(tx)
require.True(t, errors.Is(err, ErrNativeContractWitness), "got: %v", err)

View file

@ -119,8 +119,9 @@ func NewContractMD(name string, id int32) *ContractMD {
// Therefore values are taken from C# node.
c.NEF.Header.Compiler = "neo-core-v3.0"
c.NEF.Header.Magic = nef.Magic
c.NEF.Script, c.Hash = state.CreateNativeContractHash(id)
c.NEF.Script = state.CreateNativeContractScript(id)
c.NEF.Checksum = c.NEF.CalculateChecksum()
c.Hash = state.CreateContractHash(util.Uint160{}, c.NEF.Checksum, name)
c.Manifest = *manifest.DefaultManifest(name)
return c

View file

@ -8,12 +8,12 @@ import (
// Compatibility test. hashes are taken directly from C# node.
func TestNativeHashes(t *testing.T) {
require.Equal(t, "bee421fdbb3e791265d2104cb34934f53fcc0e45", newManagement().Hash.StringLE())
require.Equal(t, "4961bf0ab79370b23dc45cde29f568d0e0fa6e93", newNEO().Hash.StringLE())
require.Equal(t, "9ac04cf223f646de5f7faccafe34e30e5d4382a2", newGAS().Hash.StringLE())
require.Equal(t, "c939a4af1c762e5edca36d4b61c06ba82c4c6ff5", newPolicy().Hash.StringLE())
require.Equal(t, "f4bbd95569e8dda2cb84eb609a1488ddd0d9fa91", newDesignate(false).Hash.StringLE())
require.Equal(t, "8cd3889136056b3304ec59f6d424b8767710ed79", newOracle().Hash.StringLE())
require.Equal(t, "a501d7d7d10983673b61b7a2d3a813b36f9f0e43", newManagement().Hash.StringLE())
require.Equal(t, "f617baca689d1abddedda7c3b80675c4ac21e932", newNEO().Hash.StringLE())
require.Equal(t, "75844530eb44f4715a42950bb59b4d7ace0b2f3d", newGAS().Hash.StringLE())
require.Equal(t, "e21a28cfc1e662e152f668c86198141cc17b6c37", newPolicy().Hash.StringLE())
require.Equal(t, "69b1909aaa14143b0624ba0d61d5cd3b8b67529d", newDesignate(false).Hash.StringLE())
require.Equal(t, "b82bbf650f963dbf71577d10ea4077e711a13e7b", newOracle().Hash.StringLE())
// Not yet a part of NEO.
//require.Equal(t, "", newNotary().Hash.StringLE()())
}

View file

@ -239,7 +239,7 @@ func (m *Management) markUpdated(h util.Uint160) {
// Deploy creates contract's hash/ID and saves new contract into the given DAO.
// It doesn't run _deploy method and doesn't emit notification.
func (m *Management) Deploy(d dao.DAO, sender util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) {
h := state.CreateContractHash(sender, neff.Script)
h := state.CreateContractHash(sender, neff.Checksum, manif.Name)
key := makeContractKey(h)
si := d.GetStorageItem(m.ContractID, key)
if si != nil {

View file

@ -18,13 +18,13 @@ func TestDeployGetUpdateDestroyContract(t *testing.T) {
d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
script := []byte{1}
sender := util.Uint160{1, 2, 3}
h := state.CreateContractHash(sender, script)
ne, err := nef.NewFile(script)
require.NoError(t, err)
manif := manifest.NewManifest("Test")
require.NoError(t, err)
h := state.CreateContractHash(sender, ne.Checksum, manif.Name)
contract, err := mgmt.Deploy(d, sender, ne, manif)
require.NoError(t, err)
require.Equal(t, int32(1), contract.ID)
@ -43,7 +43,7 @@ func TestDeployGetUpdateDestroyContract(t *testing.T) {
require.NoError(t, err)
require.Equal(t, int32(2), contract2.ID)
require.Equal(t, uint16(0), contract2.UpdateCounter)
require.Equal(t, state.CreateContractHash(sender2, script), contract2.Hash)
require.Equal(t, state.CreateContractHash(sender2, ne.Checksum, manif.Name), contract2.Hash)
require.Equal(t, ne, &contract2.NEF)
require.Equal(t, *manif, contract2.Manifest)

View file

@ -36,7 +36,7 @@ func TestRestoreAfterDeploy(t *testing.T) {
mgmtHash := bc.ManagementContractHash()
cs1, _ := getTestContractState(bc)
cs1.ID = 1
cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Script)
cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Checksum, cs1.Manifest.Name)
manif1, err := json.Marshal(cs1.Manifest)
require.NoError(t, err)
nef1, err := nef.NewFile(cs1.NEF.Script)
@ -80,7 +80,7 @@ func TestContractDeploy(t *testing.T) {
mgmtHash := bc.ManagementContractHash()
cs1, _ := getTestContractState(bc)
cs1.ID = 1
cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Script)
cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Checksum, cs1.Manifest.Name)
manif1, err := json.Marshal(cs1.Manifest)
require.NoError(t, err)
nef1b, err := cs1.NEF.Bytes()
@ -214,7 +214,7 @@ func TestContractDeploy(t *testing.T) {
checkFAULTState(t, res)
t.Run("get after failed deploy", func(t *testing.T) {
h := state.CreateContractHash(neoOwner, deployScript)
h := state.CreateContractHash(neoOwner, nefD.Checksum, m.Name)
checkContractState(t, bc, h, nil)
})
})
@ -243,7 +243,7 @@ func TestContractDeploy(t *testing.T) {
checkFAULTState(t, res)
t.Run("get after bad _deploy", func(t *testing.T) {
h := state.CreateContractHash(neoOwner, deployScript)
h := state.CreateContractHash(neoOwner, nefD.Checksum, m.Name)
checkContractState(t, bc, h, nil)
})
})

View file

@ -112,25 +112,25 @@ func (c *Contract) FromStackItem(item stackitem.Item) error {
// CreateContractHash creates deployed contract hash from transaction sender
// and contract script.
func CreateContractHash(sender util.Uint160, script []byte) util.Uint160 {
func CreateContractHash(sender util.Uint160, checksum uint32, name string) util.Uint160 {
w := io.NewBufBinWriter()
emit.Opcodes(w.BinWriter, opcode.ABORT)
emit.Bytes(w.BinWriter, sender.BytesBE())
emit.Bytes(w.BinWriter, script)
emit.Int(w.BinWriter, int64(checksum))
emit.String(w.BinWriter, name)
if w.Err != nil {
panic(w.Err)
}
return hash.Hash160(w.Bytes())
}
// CreateNativeContractHash returns script and hash for the native contract.
func CreateNativeContractHash(id int32) ([]byte, util.Uint160) {
// CreateNativeContractScript returns script for the native contract.
func CreateNativeContractScript(id int32) []byte {
w := io.NewBufBinWriter()
emit.Int(w.BinWriter, int64(id))
emit.Syscall(w.BinWriter, interopnames.SystemContractCallNative)
if w.Err != nil {
panic(w.Err)
}
script := w.Bytes()
return script, CreateContractHash(util.Uint160{}, script)
return w.Bytes()
}

View file

@ -62,14 +62,22 @@ func TestEncodeDecodeContractState(t *testing.T) {
}
func TestCreateContractHash(t *testing.T) {
var script = []byte{1, 2, 3}
var neff = nef.File{
Header: nef.Header{
Compiler: "test",
Magic: nef.Magic,
},
Tokens: []nef.MethodToken{},
Script: []byte{1, 2, 3},
}
var sender util.Uint160
var err error
require.Equal(t, "b8e95ff7b11c427c29355e3398722d97bd2ca069", CreateContractHash(sender, script).StringLE())
neff.Checksum = neff.CalculateChecksum()
require.Equal(t, "9b9628e4f1611af90e761eea8cc21372380c74b6", CreateContractHash(sender, neff.Checksum, "").StringLE())
sender, err = util.Uint160DecodeStringLE("a400ff00ff00ff00ff00ff00ff00ff00ff00ff01")
require.NoError(t, err)
require.Equal(t, "435c467b8e15cb9b1474ad7ee817ffdcfededef9", CreateContractHash(sender, script).StringLE())
require.Equal(t, "66eec404d86b918d084e62a29ac9990e3b6f4286", CreateContractHash(sender, neff.Checksum, "").StringLE())
}
func TestContractFromStackItem(t *testing.T) {

View file

@ -56,11 +56,11 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{})
}
const testContractHash = "0b3bc97e94ed99e32dda46c9ecd2d3626979af06"
const deploymentTxHash = "e3a67acac29014dc8c24773752ac4535a0f020486749ec5c907234fc9328246c"
const testContractHash = "c6436aab21ebd15279b85af8d7b5808d38455b0a"
const deploymentTxHash = "e6ffce4533231c4efdea9a65c7abc0e7073d96a4ebc66f402db3a84b6f8939ef"
const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70"
const verifyContractHash = "d2da8ee8c0bf6c5bf3dda1ef671dbf5fef7226e9"
const verifyContractHash = "03ffc0897543b9b709e0f8cab4a7682dae0ba943"
const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740"
const testVerifyContractAVM = "VwcADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGgRVUH4J+yMIaonBwAAABFADBQNDwMCCQACAQMHAwQFAgEADgYMCdswcWkRVUH4J+yMIaonBwAAABJAE0A="

Binary file not shown.