github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/signer/fourbyte/fourbyte.go (about)

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