Skip to content

Commit

Permalink
Merge pull request #1 from binance-chain/precompile-contract
Browse files Browse the repository at this point in the history
R4R: implement precompile contract
  • Loading branch information
HaoyangLiu authored Mar 17, 2020
2 parents 58cf568 + f7a1cf4 commit 8c2fe18
Show file tree
Hide file tree
Showing 11 changed files with 1,120 additions and 51 deletions.
21 changes: 12 additions & 9 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,18 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
// contracts used in the Istanbul release.
var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},

common.BytesToAddress([]byte{100}): &tmHeaderValidate{},
common.BytesToAddress([]byte{101}): &iavlMerkleProofValidate{},
}

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
Expand Down
134 changes: 134 additions & 0 deletions core/vm/contracts_lightclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package vm

import (
"encoding/binary"
"fmt"

"github.com/ethereum/go-ethereum/core/vm/lightclient"
"github.com/ethereum/go-ethereum/params"
)

const (
precompileContractInputMetaDataLength uint64 = 32
consensusStateLengthBytesLength uint64 = 32

tmHeaderValidateResultMetaDataLength uint64 = 32
merkleProofValidateResultLength uint64 = 32
)

// input:
// consensus state length | consensus state | tendermint header |
// 32 bytes | | |
func decodeTendermintHeaderValidationInput(input []byte) (*lightclient.ConsensusState, *lightclient.Header, error) {
csLen := binary.BigEndian.Uint64(input[consensusStateLengthBytesLength-8 : consensusStateLengthBytesLength])
if uint64(len(input)) <= consensusStateLengthBytesLength+csLen {
return nil, nil, fmt.Errorf("expected payload size %d, actual size: %d", consensusStateLengthBytesLength+csLen, len(input))
}

cs, err := lightclient.DecodeConsensusState(input[consensusStateLengthBytesLength : consensusStateLengthBytesLength+csLen])
if err != nil {
return nil, nil, err
}
header, err := lightclient.DecodeHeader(input[consensusStateLengthBytesLength+csLen:])
if err != nil {
return nil, nil, err
}

return &cs, header, nil
}

// tmHeaderValidate implemented as a native contract.
type tmHeaderValidate struct{}

func (c *tmHeaderValidate) RequiredGas(input []byte) uint64 {
return params.TendermintHeaderValidateGas
}

func (c *tmHeaderValidate) Run(input []byte) (result []byte, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("internal error: %v\n", r)
}
}()

if uint64(len(input)) <= precompileContractInputMetaDataLength {
return nil, fmt.Errorf("invalid input")
}

payloadLength := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength-8 : precompileContractInputMetaDataLength])
if uint64(len(input)) != payloadLength+precompileContractInputMetaDataLength {
return nil, fmt.Errorf("invalid input: input size should be %d, actual the size is %d", payloadLength+precompileContractInputMetaDataLength, len(input))
}

cs, header, err := decodeTendermintHeaderValidationInput(input[precompileContractInputMetaDataLength:])
if err != nil {
return nil, err
}

validatorSetChanged, err := cs.ApplyHeader(header)
if err != nil {
return nil, err
}

consensusStateBytes, err := cs.EncodeConsensusState()
if err != nil {
return nil, err
}

// result
// | validatorSetChanged | empty | consensusStateBytesLength | new consensusState |
// | 1 byte | 23 bytes | 8 bytes | |
lengthBytes := make([]byte, tmHeaderValidateResultMetaDataLength)
if validatorSetChanged {
copy(lengthBytes[:1], []byte{0x01})
}
consensusStateBytesLength := uint64(len(consensusStateBytes))
binary.BigEndian.PutUint64(lengthBytes[tmHeaderValidateResultMetaDataLength-8:], consensusStateBytesLength)

result = append(lengthBytes, consensusStateBytes...)

return result, nil
}

//------------------------------------------------------------------------------------------------------------------------------------------------

// tmHeaderValidate implemented as a native contract.
type iavlMerkleProofValidate struct{}

func (c *iavlMerkleProofValidate) RequiredGas(input []byte) uint64 {
return params.IAVLMerkleProofValidateGas
}

// input:
// | payload length | payload |
// | 32 bytes | |
func (c *iavlMerkleProofValidate) Run(input []byte) (result []byte, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("internal error: %v\n", r)
}
}()

if uint64(len(input)) <= precompileContractInputMetaDataLength {
return nil, fmt.Errorf("invalid input: input should include %d bytes payload length and payload", precompileContractInputMetaDataLength)
}

payloadLength := binary.BigEndian.Uint64(input[precompileContractInputMetaDataLength-8 : precompileContractInputMetaDataLength])
if uint64(len(input)) != payloadLength+precompileContractInputMetaDataLength {
return nil, fmt.Errorf("invalid input: input size should be %d, actual the size is %d", payloadLength+precompileContractInputMetaDataLength, len(input))
}

kvmp, err := lightclient.DecodeKeyValueMerkleProof(input[precompileContractInputMetaDataLength:])
if err != nil {
return nil, err
}

valid := kvmp.Validate()
if !valid {
return nil, fmt.Errorf("invalid merkle proof")
}

result = make([]byte, merkleProofValidateResultLength)
binary.BigEndian.PutUint64(result[merkleProofValidateResultLength-8:], 0x01)
return result, nil
}
104 changes: 104 additions & 0 deletions core/vm/contracts_lightclient_test.go

Large diffs are not rendered by default.

138 changes: 138 additions & 0 deletions core/vm/lightclient/multistoreproof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package lightclient

import (
"bytes"
"fmt"

"github.com/tendermint/iavl"
"github.com/tendermint/tendermint/crypto/merkle"
cmn "github.com/tendermint/tendermint/libs/common"
)

// MultiStoreProof defines a collection of store proofs in a multi-store
type MultiStoreProof struct {
StoreInfos []StoreInfo
}

func NewMultiStoreProof(storeInfos []StoreInfo) *MultiStoreProof {
return &MultiStoreProof{StoreInfos: storeInfos}
}

// ComputeRootHash returns the root hash for a given multi-store proof.
func (proof *MultiStoreProof) ComputeRootHash() []byte {
ci := CommitInfo{
Version: -1, // TODO: Not needed; improve code.
StoreInfos: proof.StoreInfos,
}
return ci.Hash()
}

// RequireProof return whether proof is require for the subpath
func RequireProof(subpath string) bool {
// XXX: create a better convention.
// Currently, only when query subpath is "/store" or "/key", will proof be included in response.
// If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go:212
if subpath == "/store" || subpath == "/key" {
return true
}
return false
}

//-----------------------------------------------------------------------------

var _ merkle.ProofOperator = MultiStoreProofOp{}

// the multi-store proof operation constant value
const ProofOpMultiStore = "multistore"

// TODO: document
type MultiStoreProofOp struct {
// Encoded in ProofOp.Key
key []byte

// To encode in ProofOp.Data.
Proof *MultiStoreProof `json:"proof"`
}

func NewMultiStoreProofOp(key []byte, proof *MultiStoreProof) MultiStoreProofOp {
return MultiStoreProofOp{
key: key,
Proof: proof,
}
}

// MultiStoreProofOpDecoder returns a multi-store merkle proof operator from a
// given proof operation.
func MultiStoreProofOpDecoder(pop merkle.ProofOp) (merkle.ProofOperator, error) {
if pop.Type != ProofOpMultiStore {
return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpMultiStore)
}

// XXX: a bit strange as we'll discard this, but it works
var op MultiStoreProofOp

err := Cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op)
if err != nil {
return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into MultiStoreProofOp")
}

return NewMultiStoreProofOp(pop.Key, op.Proof), nil
}

// ProofOp return a merkle proof operation from a given multi-store proof
// operation.
func (op MultiStoreProofOp) ProofOp() merkle.ProofOp {
bz := Cdc.MustMarshalBinaryLengthPrefixed(op)
return merkle.ProofOp{
Type: ProofOpMultiStore,
Key: op.key,
Data: bz,
}
}

// String implements the Stringer interface for a mult-store proof operation.
func (op MultiStoreProofOp) String() string {
return fmt.Sprintf("MultiStoreProofOp{%v}", op.GetKey())
}

// GetKey returns the key for a multi-store proof operation.
func (op MultiStoreProofOp) GetKey() []byte {
return op.key
}

// Run executes a multi-store proof operation for a given value. It returns
// the root hash if the value matches all the store's commitID's hash or an
// error otherwise.
func (op MultiStoreProofOp) Run(args [][]byte) ([][]byte, error) {
if len(args) != 1 {
return nil, cmn.NewError("Value size is not 1")
}

value := args[0]
root := op.Proof.ComputeRootHash()

for _, si := range op.Proof.StoreInfos {
if si.Name == string(op.key) {
if bytes.Equal(value, si.Core.CommitID.Hash) {
return [][]byte{root}, nil
}

return nil, cmn.NewError("hash mismatch for substore %v: %X vs %X", si.Name, si.Core.CommitID.Hash, value)
}
}

return nil, cmn.NewError("key %v not found in multistore proof", op.key)
}

//-----------------------------------------------------------------------------

// XXX: This should be managed by the rootMultiStore which may want to register
// more proof ops?
func DefaultProofRuntime() (prt *merkle.ProofRuntime) {
prt = merkle.NewProofRuntime()
prt.RegisterOpDecoder(merkle.ProofOpSimpleValue, merkle.SimpleValueOpDecoder)
prt.RegisterOpDecoder(iavl.ProofOpIAVLValue, iavl.IAVLValueOpDecoder)
prt.RegisterOpDecoder(iavl.ProofOpIAVLAbsence, iavl.IAVLAbsenceOpDecoder)
prt.RegisterOpDecoder(ProofOpMultiStore, MultiStoreProofOpDecoder)
return
}
86 changes: 86 additions & 0 deletions core/vm/lightclient/rootmultistore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package lightclient

import (
"fmt"

"github.com/tendermint/tendermint/crypto/merkle"
"github.com/tendermint/tendermint/crypto/tmhash"
)

//----------------------------------------
// CommitID

// CommitID contains the tree version number and its merkle root.
type CommitID struct {
Version int64
Hash []byte
}

func (cid CommitID) IsZero() bool { //nolint
return cid.Version == 0 && len(cid.Hash) == 0
}

func (cid CommitID) String() string {
return fmt.Sprintf("CommitID{%v:%X}", cid.Hash, cid.Version)
}

//----------------------------------------
// CommitInfo

// NOTE: Keep CommitInfo a simple immutable struct.
type CommitInfo struct {

// Version
Version int64

// Store info for
StoreInfos []StoreInfo
}

// Hash returns the simple merkle root hash of the stores sorted by name.
func (ci CommitInfo) Hash() []byte {
// TODO cache to ci.hash []byte
m := make(map[string][]byte, len(ci.StoreInfos))
for _, storeInfo := range ci.StoreInfos {
m[storeInfo.Name] = storeInfo.Hash()
}
return merkle.SimpleHashFromMap(m)
}

func (ci CommitInfo) CommitID() CommitID {
return CommitID{
Version: ci.Version,
Hash: ci.Hash(),
}
}

//----------------------------------------
// StoreInfo

// StoreInfo contains the name and core reference for an
// underlying store. It is the leaf of the rootMultiStores top
// level simple merkle tree.
type StoreInfo struct {
Name string
Core StoreCore
}

type StoreCore struct {
// StoreType StoreType
CommitID CommitID
// ... maybe add more state
}

// Implements merkle.Hasher.
func (si StoreInfo) Hash() []byte {
// Doesn't write Name, since merkle.SimpleHashFromMap() will
// include them via the keys.
bz, _ := Cdc.MarshalBinaryLengthPrefixed(si.Core) // Does not error
hasher := tmhash.New()
_, err := hasher.Write(bz)
if err != nil {
// TODO: Handle with #870
panic(err)
}
return hasher.Sum(nil)
}
Loading

0 comments on commit 8c2fe18

Please sign in to comment.