github.com/flashbots/go-ethereum@v1.9.7/signer/fourbyte/fourbyte.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  //go:generate go-bindata -nometadata -nocompress -o 4byte.go -pkg fourbyte 4byte.json
    18  //go:generate gofmt -s -w 4byte.go
    19  //go:generate sh -c "sed 's#var __4byteJson#//nolint:misspell\\\n&#' 4byte.go > 4byte.go.tmp && mv 4byte.go.tmp 4byte.go"
    20  
    21  // Package fourbyte contains the 4byte database.
    22  package fourbyte
    23  
    24  import (
    25  	"encoding/hex"
    26  	"encoding/json"
    27  	"fmt"
    28  	"io/ioutil"
    29  	"os"
    30  )
    31  
    32  // Database is a 4byte database with the possibility of maintaining an immutable
    33  // set (embedded) into the process and a mutable set (loaded and written to file).
    34  type Database struct {
    35  	embedded   map[string]string
    36  	custom     map[string]string
    37  	customPath string
    38  }
    39  
    40  // newEmpty exists for testing purposes.
    41  func newEmpty() *Database {
    42  	return &Database{
    43  		embedded: make(map[string]string),
    44  		custom:   make(map[string]string),
    45  	}
    46  }
    47  
    48  // New loads the standard signature database embedded in the package.
    49  func New() (*Database, error) {
    50  	return NewWithFile("")
    51  }
    52  
    53  // NewFromFile loads signature database from file, and errors if the file is not
    54  // valid JSON. The constructor does no other validation of contents. This method
    55  // does not load the embedded 4byte database.
    56  //
    57  // The provided path will be used to write new values into if they are submitted
    58  // via the API.
    59  func NewFromFile(path string) (*Database, error) {
    60  	raw, err := os.Open(path)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	defer raw.Close()
    65  
    66  	db := newEmpty()
    67  	if err := json.NewDecoder(raw).Decode(&db.embedded); err != nil {
    68  		return nil, err
    69  	}
    70  	return db, nil
    71  }
    72  
    73  // NewWithFile loads both the standard signature database (embedded resource
    74  // file) as well as a custom database. The latter will be used to write new
    75  // values into if they are submitted via the API.
    76  func NewWithFile(path string) (*Database, error) {
    77  	db := &Database{make(map[string]string), make(map[string]string), path}
    78  	db.customPath = path
    79  
    80  	blob, err := Asset("4byte.json")
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	if err := json.Unmarshal(blob, &db.embedded); err != nil {
    85  		return nil, err
    86  	}
    87  	// Custom file may not exist. Will be created during save, if needed.
    88  	if _, err := os.Stat(path); err == nil {
    89  		if blob, err = ioutil.ReadFile(path); err != nil {
    90  			return nil, err
    91  		}
    92  		if err := json.Unmarshal(blob, &db.custom); err != nil {
    93  			return nil, err
    94  		}
    95  	}
    96  	return db, nil
    97  }
    98  
    99  // Size returns the number of 4byte entries in the embedded and custom datasets.
   100  func (db *Database) Size() (int, int) {
   101  	return len(db.embedded), len(db.custom)
   102  }
   103  
   104  // Selector checks the given 4byte ID against the known ABI methods.
   105  //
   106  // This method does not validate the match, it's assumed the caller will do.
   107  func (db *Database) Selector(id []byte) (string, error) {
   108  	if len(id) < 4 {
   109  		return "", fmt.Errorf("expected 4-byte id, got %d", len(id))
   110  	}
   111  	sig := hex.EncodeToString(id[:4])
   112  	if selector, exists := db.embedded[sig]; exists {
   113  		return selector, nil
   114  	}
   115  	if selector, exists := db.custom[sig]; exists {
   116  		return selector, nil
   117  	}
   118  	return "", fmt.Errorf("signature %v not found", sig)
   119  }
   120  
   121  // AddSelector inserts a new 4byte entry into the database. If custom database
   122  // saving is enabled, the new dataset is also persisted to disk.
   123  //
   124  // Node, this method does _not_ validate the correctness of the data. It assumes
   125  // the caller has already done so.
   126  func (db *Database) AddSelector(selector string, data []byte) error {
   127  	// If the selector is already known, skip duplicating it
   128  	if len(data) < 4 {
   129  		return nil
   130  	}
   131  	if _, err := db.Selector(data[:4]); err == nil {
   132  		return nil
   133  	}
   134  	// Inject the custom selector into the database and persist if needed
   135  	db.custom[hex.EncodeToString(data[:4])] = selector
   136  	if db.customPath == "" {
   137  		return nil
   138  	}
   139  	blob, err := json.Marshal(db.custom)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	return ioutil.WriteFile(db.customPath, blob, 0600)
   144  }