github.com/ethereumfair/go-ethereum@v1.0.18/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  // Package fourbyte contains the 4byte database.
    18  package fourbyte
    19  
    20  import (
    21  	_ "embed"
    22  	"encoding/hex"
    23  	"encoding/json"
    24  	"fmt"
    25  	"os"
    26  )
    27  
    28  //go:embed 4byte.json
    29  var embeddedJSON []byte
    30  
    31  // Database is a 4byte database with the possibility of maintaining an immutable
    32  // set (embedded) into the process and a mutable set (loaded and written to file).
    33  type Database struct {
    34  	embedded   map[string]string
    35  	custom     map[string]string
    36  	customPath string
    37  }
    38  
    39  // newEmpty exists for testing purposes.
    40  func newEmpty() *Database {
    41  	return &Database{
    42  		embedded: make(map[string]string),
    43  		custom:   make(map[string]string),
    44  	}
    45  }
    46  
    47  // New loads the standard signature database embedded in the package.
    48  func New() (*Database, error) {
    49  	return NewWithFile("")
    50  }
    51  
    52  // NewFromFile loads signature database from file, and errors if the file is not
    53  // valid JSON. The constructor does no other validation of contents. This method
    54  // does not load the embedded 4byte database.
    55  //
    56  // The provided path will be used to write new values into if they are submitted
    57  // via the API.
    58  func NewFromFile(path string) (*Database, error) {
    59  	raw, err := os.Open(path)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	defer raw.Close()
    64  
    65  	db := newEmpty()
    66  	if err := json.NewDecoder(raw).Decode(&db.embedded); err != nil {
    67  		return nil, err
    68  	}
    69  	return db, nil
    70  }
    71  
    72  // NewWithFile loads both the standard signature database (embedded resource
    73  // file) as well as a custom database. The latter will be used to write new
    74  // values into if they are submitted via the API.
    75  func NewWithFile(path string) (*Database, error) {
    76  	db := &Database{make(map[string]string), make(map[string]string), path}
    77  	db.customPath = path
    78  
    79  	if err := json.Unmarshal(embeddedJSON, &db.embedded); err != nil {
    80  		return nil, err
    81  	}
    82  	// Custom file may not exist. Will be created during save, if needed.
    83  	if _, err := os.Stat(path); err == nil {
    84  		var blob []byte
    85  		if blob, err = os.ReadFile(path); err != nil {
    86  			return nil, err
    87  		}
    88  		if err := json.Unmarshal(blob, &db.custom); err != nil {
    89  			return nil, err
    90  		}
    91  	}
    92  	return db, nil
    93  }
    94  
    95  // Size returns the number of 4byte entries in the embedded and custom datasets.
    96  func (db *Database) Size() (int, int) {
    97  	return len(db.embedded), len(db.custom)
    98  }
    99  
   100  // Selector checks the given 4byte ID against the known ABI methods.
   101  //
   102  // This method does not validate the match, it's assumed the caller will do.
   103  func (db *Database) Selector(id []byte) (string, error) {
   104  	if len(id) < 4 {
   105  		return "", fmt.Errorf("expected 4-byte id, got %d", len(id))
   106  	}
   107  	sig := hex.EncodeToString(id[:4])
   108  	if selector, exists := db.embedded[sig]; exists {
   109  		return selector, nil
   110  	}
   111  	if selector, exists := db.custom[sig]; exists {
   112  		return selector, nil
   113  	}
   114  	return "", fmt.Errorf("signature %v not found", sig)
   115  }
   116  
   117  // AddSelector inserts a new 4byte entry into the database. If custom database
   118  // saving is enabled, the new dataset is also persisted to disk.
   119  //
   120  // Node, this method does _not_ validate the correctness of the data. It assumes
   121  // the caller has already done so.
   122  func (db *Database) AddSelector(selector string, data []byte) error {
   123  	// If the selector is already known, skip duplicating it
   124  	if len(data) < 4 {
   125  		return nil
   126  	}
   127  	if _, err := db.Selector(data[:4]); err == nil {
   128  		return nil
   129  	}
   130  	// Inject the custom selector into the database and persist if needed
   131  	db.custom[hex.EncodeToString(data[:4])] = selector
   132  	if db.customPath == "" {
   133  		return nil
   134  	}
   135  	blob, err := json.Marshal(db.custom)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	return os.WriteFile(db.customPath, blob, 0600)
   140  }