diff --git a/api/accounts/accounts.go b/api/accounts/accounts.go index c8cf27ac..68b36279 100644 --- a/api/accounts/accounts.go +++ b/api/accounts/accounts.go @@ -355,5 +355,6 @@ func (a *Accounts) Mount(root *mux.Router, pathPrefix string) { sub.Path("/{address}/storage/{key}").Methods("GET").HandlerFunc(utils.WrapHandlerFunc(a.handleGetStorage)) sub.Path("").Methods("POST").HandlerFunc(utils.WrapHandlerFunc(a.handleCallContract)) sub.Path("/{address}").Methods("POST").HandlerFunc(utils.WrapHandlerFunc(a.handleCallContract)) + sub.Path("/proof/{address}/{key}").Methods("POST").HandlerFunc(utils.WrapHandlerFunc(a.handleProofStorage)) } diff --git a/api/accounts/proof.go b/api/accounts/proof.go new file mode 100644 index 00000000..d2bf4ba6 --- /dev/null +++ b/api/accounts/proof.go @@ -0,0 +1,72 @@ +package accounts + +import ( + "net/http" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/gorilla/mux" + "github.com/pkg/errors" + "github.com/vechain/thor/api/utils" + "github.com/vechain/thor/chain" + "github.com/vechain/thor/thor" +) + +type merkleProof struct { + StateRoot string `json:"stateRoot"` + Account string `json:"account"` + Slot string `json:"slot"` + Value string `json:"value"` + AccountProof []string `json:"accountProof"` + StorageProof []string `json:"storageProof"` +} + +func (a *Accounts) proofStorage(addr thor.Address, slot thor.Bytes32, summary *chain.BlockSummary) (p *merkleProof, err error) { + state := a.stater. + NewState(summary.Header.StateRoot(), summary.Header.Number(), summary.Conflicts, summary.SteadyNum) + + proof, storageProofBytes, value, err := state.ProveStorage(addr, slot) + if err != nil { + return nil, err + } + + var accountProof []string + for _, p := range proof { + accountProof = append(accountProof, hexutil.Encode(p)) + } + + var storageProof []string + for _, p := range storageProofBytes { + storageProof = append(storageProof, hexutil.Encode(p)) + } + + return &merkleProof{ + StateRoot: summary.Header.StateRoot().String(), + Account: hexutil.Encode(addr[:]), + Slot: hexutil.Encode(slot[:]), + Value: hexutil.Encode(value[:]), + AccountProof: accountProof, + StorageProof: storageProof, + }, nil +} + +func (a *Accounts) handleProofStorage(w http.ResponseWriter, req *http.Request) error { + hexAddr := mux.Vars(req)["address"] + addr, err := thor.ParseAddress(hexAddr) + if err != nil { + return utils.BadRequest(errors.WithMessage(err, "address")) + } + slot, err := thor.ParseBytes32(mux.Vars(req)["key"]) + if err != nil { + return utils.BadRequest(errors.WithMessage(err, "storage")) + } + summary, err := a.handleRevision(req.URL.Query().Get("revision")) + if err != nil { + return err + } + proof, err := a.proofStorage(addr, slot, summary) + if err != nil { + return err + } + + return utils.WriteJSON(w, proof) +} diff --git a/muxdb/internal/trie/trie.go b/muxdb/internal/trie/trie.go index 224ca063..36856065 100644 --- a/muxdb/internal/trie/trie.go +++ b/muxdb/internal/trie/trie.go @@ -478,3 +478,18 @@ type leafAvailable struct { func (*leafAvailable) Error() string { return "leaf available" } + +type proofList [][]byte + +func (n *proofList) Put(key []byte, value []byte) error { + valueCopy := make([]byte, len(value)) + copy(valueCopy, value) + *n = append(*n, valueCopy) + return nil +} + +func (t *Trie) Prove(key []byte, fromLevel uint) ([][]byte, error) { + var proofList proofList + err := t.ext.Prove(key, fromLevel, &proofList) + return proofList, err +} diff --git a/state/state.go b/state/state.go index 8bbf69fb..979a8416 100644 --- a/state/state.go +++ b/state/state.go @@ -538,6 +538,47 @@ func (s *State) Stage(newBlockNum, newBlockConflicts uint32) (*Stage, error) { }, nil } +func (s *State) ProveStorage(addr thor.Address, slot thor.Bytes32) ([][]byte, [][]byte, thor.Bytes32, error) { + secured := thor.Blake2b(addr[:]) + securedSlot := thor.Blake2b(slot[:]) + + // ensure all nodes are loaded + // otherwise the Prove call below may fail sometimes + _, _, err := s.trie.Get(secured[:]) + if err != nil { + return nil, nil, thor.Bytes32{}, err + } + + proof, err := s.trie.Prove(secured[:], 0) + if err != nil { + return nil, nil, thor.Bytes32{}, err + } + + storageTrie, err := s.BuildStorageTrie(addr) + if err != nil { + return nil, nil, thor.Bytes32{}, err + } + + v, err := s.GetStorage(addr, slot) + if err != nil { + return nil, nil, thor.Bytes32{}, err + } + + // ensure all nodes are loaded + // otherwise the Prove call below may fail sometimes + _, _, err = storageTrie.Get(securedSlot[:]) + if err != nil { + return nil, nil, thor.Bytes32{}, err + } + + storageProofBytes, err := storageTrie.Prove(securedSlot[:], 0) + if err != nil { + return nil, nil, thor.Bytes32{}, err + } + + return proof, storageProofBytes, v, nil +} + type ( storageKey struct { addr thor.Address diff --git a/trie/proof.go b/trie/proof.go index 2ad27684..3d279a0b 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -18,6 +18,7 @@ package trie import ( "bytes" + "errors" "fmt" "github.com/ethereum/go-ethereum/log" @@ -91,6 +92,13 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb DatabaseWriter) error { return nil } +func (t *ExtendedTrie) Prove(key []byte, fromLevel uint, proofDb DatabaseWriter) error { + if t.IsNonCrypto() { + return errors.New("non-crypto") + } + return t.trie.Prove(key, fromLevel, proofDb) +} + // VerifyProof checks merkle proofs. The given proof must contain the // value for key in a trie with the given root hash. VerifyProof // returns an error if the proof contains invalid trie nodes or the