github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/ledger.go (about)

     1  package native
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  
     7  	"github.com/nspcc-dev/neo-go/pkg/config"
     8  	"github.com/nspcc-dev/neo-go/pkg/core/dao"
     9  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    10  	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
    11  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    12  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    13  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    14  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    15  	"github.com/nspcc-dev/neo-go/pkg/util"
    16  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    17  	"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
    18  )
    19  
    20  // Ledger provides an interface to blocks/transactions storage for smart
    21  // contracts. It's not a part of the proper chain's state, so it's just a
    22  // proxy between regular Blockchain/DAO interface and smart contracts.
    23  type Ledger struct {
    24  	interop.ContractMD
    25  }
    26  
    27  const ledgerContractID = -4
    28  
    29  // newLedger creates a new Ledger native contract.
    30  func newLedger() *Ledger {
    31  	var l = &Ledger{
    32  		ContractMD: *interop.NewContractMD(nativenames.Ledger, ledgerContractID),
    33  	}
    34  	defer l.BuildHFSpecificMD(l.ActiveIn())
    35  
    36  	desc := newDescriptor("currentHash", smartcontract.Hash256Type)
    37  	md := newMethodAndPrice(l.currentHash, 1<<15, callflag.ReadStates)
    38  	l.AddMethod(md, desc)
    39  
    40  	desc = newDescriptor("currentIndex", smartcontract.IntegerType)
    41  	md = newMethodAndPrice(l.currentIndex, 1<<15, callflag.ReadStates)
    42  	l.AddMethod(md, desc)
    43  
    44  	desc = newDescriptor("getBlock", smartcontract.ArrayType,
    45  		manifest.NewParameter("indexOrHash", smartcontract.ByteArrayType))
    46  	md = newMethodAndPrice(l.getBlock, 1<<15, callflag.ReadStates)
    47  	l.AddMethod(md, desc)
    48  
    49  	desc = newDescriptor("getTransaction", smartcontract.ArrayType,
    50  		manifest.NewParameter("hash", smartcontract.Hash256Type))
    51  	md = newMethodAndPrice(l.getTransaction, 1<<15, callflag.ReadStates)
    52  	l.AddMethod(md, desc)
    53  
    54  	desc = newDescriptor("getTransactionHeight", smartcontract.IntegerType,
    55  		manifest.NewParameter("hash", smartcontract.Hash256Type))
    56  	md = newMethodAndPrice(l.getTransactionHeight, 1<<15, callflag.ReadStates)
    57  	l.AddMethod(md, desc)
    58  
    59  	desc = newDescriptor("getTransactionFromBlock", smartcontract.ArrayType,
    60  		manifest.NewParameter("blockIndexOrHash", smartcontract.ByteArrayType),
    61  		manifest.NewParameter("txIndex", smartcontract.IntegerType))
    62  	md = newMethodAndPrice(l.getTransactionFromBlock, 1<<16, callflag.ReadStates)
    63  	l.AddMethod(md, desc)
    64  
    65  	desc = newDescriptor("getTransactionSigners", smartcontract.ArrayType,
    66  		manifest.NewParameter("hash", smartcontract.Hash256Type))
    67  	md = newMethodAndPrice(l.getTransactionSigners, 1<<15, callflag.ReadStates)
    68  	l.AddMethod(md, desc)
    69  
    70  	desc = newDescriptor("getTransactionVMState", smartcontract.IntegerType,
    71  		manifest.NewParameter("hash", smartcontract.Hash256Type))
    72  	md = newMethodAndPrice(l.getTransactionVMState, 1<<15, callflag.ReadStates)
    73  	l.AddMethod(md, desc)
    74  
    75  	return l
    76  }
    77  
    78  // Metadata implements the Contract interface.
    79  func (l *Ledger) Metadata() *interop.ContractMD {
    80  	return &l.ContractMD
    81  }
    82  
    83  // Initialize implements the Contract interface.
    84  func (l *Ledger) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
    85  	return nil
    86  }
    87  
    88  // InitializeCache implements the Contract interface.
    89  func (l *Ledger) InitializeCache(blockHeight uint32, d *dao.Simple) error {
    90  	return nil
    91  }
    92  
    93  // OnPersist implements the Contract interface.
    94  func (l *Ledger) OnPersist(ic *interop.Context) error {
    95  	// Actual block/tx processing is done in Blockchain.storeBlock().
    96  	// Even though C# node add them to storage here, they're not
    97  	// accessible to smart contracts (see isTraceableBlock()), thus
    98  	// the end effect is the same.
    99  	return nil
   100  }
   101  
   102  // PostPersist implements the Contract interface.
   103  func (l *Ledger) PostPersist(ic *interop.Context) error {
   104  	return nil // Actual block/tx processing is done in Blockchain.storeBlock().
   105  }
   106  
   107  // ActiveIn implements the Contract interface.
   108  func (l *Ledger) ActiveIn() *config.Hardfork {
   109  	return nil
   110  }
   111  
   112  // currentHash implements currentHash SC method.
   113  func (l *Ledger) currentHash(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
   114  	return stackitem.Make(ic.CurrentBlockHash().BytesBE())
   115  }
   116  
   117  // currentIndex implements currentIndex SC method.
   118  func (l *Ledger) currentIndex(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
   119  	return stackitem.Make(ic.BlockHeight())
   120  }
   121  
   122  // getBlock implements getBlock SC method.
   123  func (l *Ledger) getBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item {
   124  	hash := getBlockHashFromItem(ic, params[0])
   125  	block, err := ic.GetBlock(hash)
   126  	if err != nil || !isTraceableBlock(ic, block.Index) {
   127  		return stackitem.Null{}
   128  	}
   129  	return block.ToStackItem()
   130  }
   131  
   132  // getTransaction returns transaction to the SC.
   133  func (l *Ledger) getTransaction(ic *interop.Context, params []stackitem.Item) stackitem.Item {
   134  	tx, h, err := getTransactionAndHeight(ic.DAO, params[0])
   135  	if err != nil || !isTraceableBlock(ic, h) {
   136  		return stackitem.Null{}
   137  	}
   138  	return tx.ToStackItem()
   139  }
   140  
   141  // getTransactionHeight returns transaction height to the SC.
   142  func (l *Ledger) getTransactionHeight(ic *interop.Context, params []stackitem.Item) stackitem.Item {
   143  	_, h, err := getTransactionAndHeight(ic.DAO, params[0])
   144  	if err != nil || !isTraceableBlock(ic, h) {
   145  		return stackitem.Make(-1)
   146  	}
   147  	return stackitem.Make(h)
   148  }
   149  
   150  // getTransactionFromBlock returns a transaction with the given index from the
   151  // block with the height or hash specified.
   152  func (l *Ledger) getTransactionFromBlock(ic *interop.Context, params []stackitem.Item) stackitem.Item {
   153  	hash := getBlockHashFromItem(ic, params[0])
   154  	index := toUint32(params[1])
   155  	block, err := ic.GetBlock(hash)
   156  	if err != nil || !isTraceableBlock(ic, block.Index) {
   157  		return stackitem.Null{}
   158  	}
   159  	if index >= uint32(len(block.Transactions)) {
   160  		panic("wrong transaction index")
   161  	}
   162  	return block.Transactions[index].ToStackItem()
   163  }
   164  
   165  // getTransactionSigners returns transaction signers to the SC.
   166  func (l *Ledger) getTransactionSigners(ic *interop.Context, params []stackitem.Item) stackitem.Item {
   167  	tx, h, err := getTransactionAndHeight(ic.DAO, params[0])
   168  	if err != nil || !isTraceableBlock(ic, h) {
   169  		return stackitem.Null{}
   170  	}
   171  	return transaction.SignersToStackItem(tx.Signers)
   172  }
   173  
   174  // getTransactionVMState returns VM state got after transaction invocation.
   175  func (l *Ledger) getTransactionVMState(ic *interop.Context, params []stackitem.Item) stackitem.Item {
   176  	hash, err := getUint256FromItem(params[0])
   177  	if err != nil {
   178  		panic(err)
   179  	}
   180  	h, _, aer, err := ic.DAO.GetTxExecResult(hash)
   181  	if err != nil || !isTraceableBlock(ic, h) {
   182  		return stackitem.Make(vmstate.None)
   183  	}
   184  	return stackitem.Make(aer.VMState)
   185  }
   186  
   187  // isTraceableBlock defines whether we're able to give information about
   188  // the block with the index specified.
   189  func isTraceableBlock(ic *interop.Context, index uint32) bool {
   190  	height := ic.BlockHeight()
   191  	MaxTraceableBlocks := ic.Chain.GetConfig().MaxTraceableBlocks
   192  	return index <= height && index+MaxTraceableBlocks > height
   193  }
   194  
   195  // getBlockHashFromItem converts the given stackitem.Item to a block hash using the given
   196  // Ledger if needed. Interop functions accept both block numbers and
   197  // block hashes as parameters, thus this function is needed. It's supposed to
   198  // be called within VM context, so it panics if anything goes wrong.
   199  func getBlockHashFromItem(ic *interop.Context, item stackitem.Item) util.Uint256 {
   200  	bigindex, err := item.TryInteger()
   201  	if err == nil && bigindex.IsUint64() {
   202  		index := bigindex.Uint64()
   203  		if index > math.MaxUint32 {
   204  			panic("bad block index")
   205  		}
   206  		if uint32(index) > ic.BlockHeight() {
   207  			panic(fmt.Errorf("no block with index %d", index))
   208  		}
   209  		return ic.Chain.GetHeaderHash(uint32(index))
   210  	}
   211  	hash, err := getUint256FromItem(item)
   212  	if err != nil {
   213  		panic(err)
   214  	}
   215  	return hash
   216  }
   217  
   218  func getUint256FromItem(item stackitem.Item) (util.Uint256, error) {
   219  	hashbytes, err := item.TryBytes()
   220  	if err != nil {
   221  		return util.Uint256{}, fmt.Errorf("failed to get hash bytes: %w", err)
   222  	}
   223  	hash, err := util.Uint256DecodeBytesBE(hashbytes)
   224  	if err != nil {
   225  		return util.Uint256{}, fmt.Errorf("failed to decode hash: %w", err)
   226  	}
   227  	return hash, nil
   228  }
   229  
   230  // getTransactionAndHeight returns a transaction and its height if it's present
   231  // on the chain. It panics if anything goes wrong.
   232  func getTransactionAndHeight(d *dao.Simple, item stackitem.Item) (*transaction.Transaction, uint32, error) {
   233  	hash, err := getUint256FromItem(item)
   234  	if err != nil {
   235  		panic(err)
   236  	}
   237  	return d.GetTransaction(hash)
   238  }